From a77dbc743f9360a84b08d3119e9e2091571392c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=82=96=E5=BB=BA?= Date: Mon, 22 Dec 2025 14:34:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE.txt | 32 + Schema/AdminAdminCreateUserRequest.json | 79 + Schema/AdminAdminUpdateUserRequest.json | 87 + Schema/AdminAuthAuthRequest.json | 24 + Schema/AdminConfigSetAppConfigRequest.json | 416 + Schema/AdminConfigSetTabbarRequest.json | 50 + .../CustomerAdminAddQuestionnairRequest.json | 30 + Schema/CustomerAdminAddRelyTypeRequest.json | 21 + Schema/CustomerAdminAddReplyRequest.json | 27 + ...omerAdminSetQuestionnairConfigRequest.json | 21 + ...ustomerAdminUpdateQuestionnairRequest.json | 54 + .../CustomerAdminUpdateRelyTypeRequest.json | 33 + Schema/CustomerAdminUpdateReplyRequest.json | 33 + .../DynamicAdminoffUpdateContentRequest.json | 0 Schema/TestRequest.json | 17 + Schema/UserTestRequest.json | 17 + Schema/UserUserRequest.json | 17 + build.example.php | 26 + composer.json | 54 + composer.lock | 1566 ++ route/app.php | 14 + think | 19 + vendor/adbario/php-dot-notation/LICENSE.md | 21 + vendor/adbario/php-dot-notation/composer.json | 29 + vendor/adbario/php-dot-notation/src/Dot.php | 601 + .../adbario/php-dot-notation/src/helpers.php | 23 + vendor/alibabacloud/client/CHANGELOG.md | 275 + vendor/alibabacloud/client/CONTRIBUTING.md | 30 + vendor/alibabacloud/client/LICENSE.md | 13 + vendor/alibabacloud/client/NOTICE.md | 88 + vendor/alibabacloud/client/README-zh-CN.md | 161 + vendor/alibabacloud/client/README.md | 162 + vendor/alibabacloud/client/UPGRADING.md | 6 + vendor/alibabacloud/client/composer.json | 116 + vendor/alibabacloud/client/src/Accept.php | 53 + .../alibabacloud/client/src/AlibabaCloud.php | 62 + .../client/src/Clients/AccessKeyClient.php | 30 + .../client/src/Clients/BearerTokenClient.php | 29 + .../client/src/Clients/Client.php | 72 + .../client/src/Clients/EcsRamRoleClient.php | 26 + .../client/src/Clients/ManageTrait.php | 98 + .../client/src/Clients/RamRoleArnClient.php | 33 + .../client/src/Clients/RsaKeyPairClient.php | 30 + .../client/src/Clients/StsClient.php | 28 + .../alibabacloud/client/src/Config/Config.php | 62 + .../alibabacloud/client/src/Config/Data.php | 3182 ++++ .../src/Credentials/AccessKeyCredential.php | 65 + .../src/Credentials/BearerTokenCredential.php | 66 + .../src/Credentials/CredentialsInterface.php | 18 + .../src/Credentials/EcsRamRoleCredential.php | 50 + .../src/Credentials/Ini/CreateTrait.php | 181 + .../src/Credentials/Ini/IniCredential.php | 209 + .../src/Credentials/Ini/OptionsTrait.php | 111 + .../Providers/CredentialsProvider.php | 170 + .../Providers/EcsRamRoleProvider.php | 128 + .../src/Credentials/Providers/Provider.php | 88 + .../Providers/RamRoleArnProvider.php | 84 + .../Providers/RsaKeyPairProvider.php | 86 + .../src/Credentials/RamRoleArnCredential.php | 110 + .../src/Credentials/Requests/AssumeRole.php | 47 + .../Requests/GenerateSessionAccessKey.php | 37 + .../src/Credentials/RsaKeyPairCredential.php | 75 + .../client/src/Credentials/StsCredential.php | 80 + .../client/src/DefaultAcsClient.php | 55 + vendor/alibabacloud/client/src/Encode.php | 64 + .../src/Exception/AlibabaCloudException.php | 70 + .../client/src/Exception/ClientException.php | 38 + .../client/src/Exception/ServerException.php | 158 + .../client/src/Filter/ApiFilter.php | 245 + .../client/src/Filter/ClientFilter.php | 139 + .../client/src/Filter/CredentialFilter.php | 152 + .../alibabacloud/client/src/Filter/Filter.php | 67 + .../client/src/Filter/HttpFilter.php | 166 + vendor/alibabacloud/client/src/Functions.php | 289 + .../client/src/Log/LogFormatter.php | 78 + .../client/src/Profile/DefaultProfile.php | 74 + .../client/src/Regions/EndpointProvider.php | 18 + .../client/src/Regions/LocationService.php | 160 + .../src/Regions/LocationServiceRequest.php | 46 + vendor/alibabacloud/client/src/Release.php | 112 + .../client/src/Request/Request.php | 445 + .../client/src/Request/RoaRequest.php | 335 + .../client/src/Request/RpcRequest.php | 203 + .../client/src/Request/Traits/AcsTrait.php | 239 + .../client/src/Request/Traits/ClientTrait.php | 98 + .../src/Request/Traits/DeprecatedRoaTrait.php | 55 + .../src/Request/Traits/DeprecatedTrait.php | 246 + .../client/src/Request/Traits/RetryTrait.php | 149 + .../client/src/Request/UserAgent.php | 142 + .../src/Resolver/ActionResolverTrait.php | 50 + .../client/src/Resolver/ApiResolver.php | 113 + .../client/src/Resolver/CallTrait.php | 67 + .../alibabacloud/client/src/Resolver/Roa.php | 43 + .../alibabacloud/client/src/Resolver/Rpc.php | 41 + .../client/src/Resolver/VersionResolver.php | 73 + .../alibabacloud/client/src/Result/Result.php | 151 + vendor/alibabacloud/client/src/SDK.php | 57 + .../src/Signature/BearerTokenSignature.php | 47 + .../src/Signature/ShaHmac1Signature.php | 47 + .../src/Signature/ShaHmac256Signature.php | 47 + .../Signature/ShaHmac256WithRsaSignature.php | 67 + .../client/src/Signature/Signature.php | 49 + .../src/Signature/SignatureInterface.php | 35 + .../client/src/Support/Arrays.php | 41 + .../alibabacloud/client/src/Support/Path.php | 28 + .../alibabacloud/client/src/Support/Sign.php | 140 + .../client/src/Traits/ArrayAccessTrait.php | 57 + .../client/src/Traits/ClientTrait.php | 273 + .../client/src/Traits/DefaultRegionTrait.php | 66 + .../client/src/Traits/EndpointTrait.php | 119 + .../client/src/Traits/HasDataTrait.php | 317 + .../client/src/Traits/HistoryTrait.php | 68 + .../client/src/Traits/HttpTrait.php | 141 + .../client/src/Traits/LogTrait.php | 64 + .../client/src/Traits/MockTrait.php | 97 + .../client/src/Traits/ObjectAccessTrait.php | 54 + .../client/src/Traits/RegionTrait.php | 33 + .../client/src/Traits/RequestTrait.php | 90 + vendor/autoload.php | 7 + vendor/bin/jp.php | 14 + vendor/bin/validate-json | 14 + .../weblibs-configmanager/.gitignore | 2 + .../weblibs-configmanager/.travis.yml | 17 + .../weblibs-configmanager/README.md | 130 + .../weblibs-configmanager/composer.json | 36 + .../weblibs-configmanager/composer.lock | 1235 ++ .../weblibs-configmanager/phpunit.xml | 19 + .../src/ConfigManager.php | 148 + .../tests/ConfigManagerTest.php | 113 + .../testsdata/sample_config_data.php | 23 + vendor/composer/ClassLoader.php | 445 + vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 67 + vendor/composer/autoload_files.php | 23 + vendor/composer/autoload_namespaces.php | 10 + vendor/composer/autoload_psr4.php | 38 + vendor/composer/autoload_real.php | 70 + vendor/composer/autoload_static.php | 291 + vendor/composer/installed.json | 1966 +++ vendor/danielstjules/stringy/CHANGELOG.md | 180 + vendor/danielstjules/stringy/LICENSE.txt | 19 + vendor/danielstjules/stringy/README.md | 1082 ++ vendor/danielstjules/stringy/composer.json | 35 + vendor/danielstjules/stringy/src/Create.php | 19 + .../stringy/src/StaticStringy.php | 161 + vendor/danielstjules/stringy/src/Stringy.php | 1986 +++ vendor/guzzlehttp/guzzle/.php_cs | 23 + vendor/guzzlehttp/guzzle/CHANGELOG.md | 1318 ++ vendor/guzzlehttp/guzzle/Dockerfile | 18 + vendor/guzzlehttp/guzzle/LICENSE | 19 + vendor/guzzlehttp/guzzle/README.md | 90 + vendor/guzzlehttp/guzzle/UPGRADING.md | 1203 ++ vendor/guzzlehttp/guzzle/composer.json | 59 + vendor/guzzlehttp/guzzle/src/Client.php | 511 + .../guzzlehttp/guzzle/src/ClientInterface.php | 87 + .../guzzle/src/Cookie/CookieJar.php | 316 + .../guzzle/src/Cookie/CookieJarInterface.php | 84 + .../guzzle/src/Cookie/FileCookieJar.php | 91 + .../guzzle/src/Cookie/SessionCookieJar.php | 72 + .../guzzle/src/Cookie/SetCookie.php | 403 + .../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 | 544 + 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/functions.php | 392 + .../guzzle/src/functions_include.php | 6 + vendor/guzzlehttp/promises/CHANGELOG.md | 65 + vendor/guzzlehttp/promises/LICENSE | 19 + vendor/guzzlehttp/promises/Makefile | 13 + vendor/guzzlehttp/promises/README.md | 504 + vendor/guzzlehttp/promises/composer.json | 34 + .../promises/src/AggregateException.php | 16 + .../promises/src/CancellationException.php | 9 + vendor/guzzlehttp/promises/src/Coroutine.php | 151 + .../guzzlehttp/promises/src/EachPromise.php | 229 + .../promises/src/FulfilledPromise.php | 82 + vendor/guzzlehttp/promises/src/Promise.php | 280 + .../promises/src/PromiseInterface.php | 93 + .../promises/src/PromisorInterface.php | 15 + .../promises/src/RejectedPromise.php | 87 + .../promises/src/RejectionException.php | 47 + vendor/guzzlehttp/promises/src/TaskQueue.php | 66 + .../promises/src/TaskQueueInterface.php | 25 + vendor/guzzlehttp/promises/src/functions.php | 457 + .../promises/src/functions_include.php | 6 + vendor/guzzlehttp/psr7/CHANGELOG.md | 246 + vendor/guzzlehttp/psr7/LICENSE | 19 + vendor/guzzlehttp/psr7/README.md | 745 + vendor/guzzlehttp/psr7/composer.json | 49 + vendor/guzzlehttp/psr7/src/AppendStream.php | 241 + vendor/guzzlehttp/psr7/src/BufferStream.php | 137 + vendor/guzzlehttp/psr7/src/CachingStream.php | 138 + vendor/guzzlehttp/psr7/src/DroppingStream.php | 42 + vendor/guzzlehttp/psr7/src/FnStream.php | 158 + vendor/guzzlehttp/psr7/src/InflateStream.php | 52 + vendor/guzzlehttp/psr7/src/LazyOpenStream.php | 39 + vendor/guzzlehttp/psr7/src/LimitStream.php | 155 + vendor/guzzlehttp/psr7/src/MessageTrait.php | 213 + .../guzzlehttp/psr7/src/MultipartStream.php | 153 + vendor/guzzlehttp/psr7/src/NoSeekStream.php | 22 + vendor/guzzlehttp/psr7/src/PumpStream.php | 165 + vendor/guzzlehttp/psr7/src/Request.php | 151 + vendor/guzzlehttp/psr7/src/Response.php | 154 + vendor/guzzlehttp/psr7/src/Rfc7230.php | 18 + vendor/guzzlehttp/psr7/src/ServerRequest.php | 376 + vendor/guzzlehttp/psr7/src/Stream.php | 267 + .../psr7/src/StreamDecoratorTrait.php | 149 + vendor/guzzlehttp/psr7/src/StreamWrapper.php | 161 + vendor/guzzlehttp/psr7/src/UploadedFile.php | 316 + vendor/guzzlehttp/psr7/src/Uri.php | 760 + vendor/guzzlehttp/psr7/src/UriNormalizer.php | 216 + vendor/guzzlehttp/psr7/src/UriResolver.php | 219 + vendor/guzzlehttp/psr7/src/functions.php | 899 ++ .../guzzlehttp/psr7/src/functions_include.php | 6 + .../justinrainbow/json-schema/.gitattributes | 5 + vendor/justinrainbow/json-schema/LICENSE | 29 + vendor/justinrainbow/json-schema/README.md | 55 + .../json-schema/bin/validate-json | 245 + .../justinrainbow/json-schema/composer.json | 58 + .../json-schema/phpunit.xml.dist | 26 + .../Constraints/CollectionConstraint.php | 112 + .../src/JsonSchema/Constraints/Constraint.php | 291 + .../Constraints/ConstraintInterface.php | 60 + .../JsonSchema/Constraints/EnumConstraint.php | 46 + .../src/JsonSchema/Constraints/Factory.php | 96 + .../Constraints/FormatConstraint.php | 181 + .../Constraints/NumberConstraint.php | 83 + .../Constraints/ObjectConstraint.php | 149 + .../Constraints/SchemaConstraint.php | 37 + .../Constraints/StringConstraint.php | 57 + .../JsonSchema/Constraints/TypeConstraint.php | 145 + .../Constraints/UndefinedConstraint.php | 307 + .../Exception/InvalidArgumentException.php | 17 + .../InvalidSchemaMediaTypeException.php | 17 + .../Exception/InvalidSourceUriException.php | 17 + .../Exception/JsonDecodingException.php | 40 + .../Exception/ResourceNotFoundException.php | 17 + .../Exception/UriResolverException.php | 17 + .../src/JsonSchema/RefResolver.php | 277 + .../Uri/Retrievers/AbstractRetriever.php | 29 + .../src/JsonSchema/Uri/Retrievers/Curl.php | 79 + .../Uri/Retrievers/FileGetContents.php | 87 + .../Uri/Retrievers/PredefinedArray.php | 54 + .../Uri/Retrievers/UriRetrieverInterface.php | 32 + .../src/JsonSchema/Uri/UriResolver.php | 157 + .../src/JsonSchema/Uri/UriRetriever.php | 289 + .../json-schema/src/JsonSchema/Validator.php | 40 + .../flysystem-cached-adapter/.editorconfig | 10 + .../flysystem-cached-adapter/.gitignore | 4 + .../league/flysystem-cached-adapter/.php_cs | 7 + .../flysystem-cached-adapter/.scrutinizer.yml | 34 + .../flysystem-cached-adapter/.travis.yml | 29 + .../league/flysystem-cached-adapter/LICENSE | 19 + .../clover/.gitignore | 2 + .../flysystem-cached-adapter/composer.json | 30 + .../flysystem-cached-adapter/phpspec.yml | 6 + .../flysystem-cached-adapter/phpunit.php | 3 + .../flysystem-cached-adapter/phpunit.xml | 29 + .../league/flysystem-cached-adapter/readme.md | 20 + .../spec/CachedAdapterSpec.php | 435 + .../src/CacheInterface.php | 101 + .../src/CachedAdapter.php | 324 + .../src/Storage/AbstractCache.php | 417 + .../src/Storage/Adapter.php | 115 + .../src/Storage/Memcached.php | 59 + .../src/Storage/Memory.php | 22 + .../src/Storage/Noop.php | 171 + .../src/Storage/PhpRedis.php | 62 + .../src/Storage/Predis.php | 75 + .../src/Storage/Psr6Cache.php | 59 + .../src/Storage/Stash.php | 60 + .../tests/AdapterCacheTests.php | 104 + .../tests/InspectionTests.php | 16 + .../tests/MemcachedTests.php | 35 + .../tests/MemoryCacheTests.php | 255 + .../tests/NoopCacheTests.php | 35 + .../tests/PhpRedisTests.php | 45 + .../tests/PredisTests.php | 55 + .../tests/Psr6CacheTest.php | 45 + .../tests/StashTest.php | 43 + vendor/league/flysystem/LICENSE | 19 + vendor/league/flysystem/composer.json | 64 + vendor/league/flysystem/deprecations.md | 19 + .../flysystem/src/Adapter/AbstractAdapter.php | 72 + .../src/Adapter/AbstractFtpAdapter.php | 693 + .../src/Adapter/CanOverwriteFiles.php | 12 + vendor/league/flysystem/src/Adapter/Ftp.php | 572 + vendor/league/flysystem/src/Adapter/Ftpd.php | 45 + vendor/league/flysystem/src/Adapter/Local.php | 532 + .../flysystem/src/Adapter/NullAdapter.php | 144 + .../Polyfill/NotSupportingVisibilityTrait.php | 33 + .../Adapter/Polyfill/StreamedCopyTrait.php | 51 + .../Adapter/Polyfill/StreamedReadingTrait.php | 44 + .../src/Adapter/Polyfill/StreamedTrait.php | 9 + .../Adapter/Polyfill/StreamedWritingTrait.php | 60 + .../flysystem/src/Adapter/SynologyFtp.php | 8 + .../league/flysystem/src/AdapterInterface.php | 118 + vendor/league/flysystem/src/Config.php | 107 + .../league/flysystem/src/ConfigAwareTrait.php | 49 + vendor/league/flysystem/src/Directory.php | 31 + vendor/league/flysystem/src/Exception.php | 8 + vendor/league/flysystem/src/File.php | 205 + .../flysystem/src/FileExistsException.php | 37 + .../flysystem/src/FileNotFoundException.php | 37 + vendor/league/flysystem/src/Filesystem.php | 408 + .../flysystem/src/FilesystemInterface.php | 284 + .../src/FilesystemNotFoundException.php | 12 + vendor/league/flysystem/src/Handler.php | 137 + vendor/league/flysystem/src/MountManager.php | 648 + .../flysystem/src/NotSupportedException.php | 37 + .../flysystem/src/Plugin/AbstractPlugin.php | 24 + .../league/flysystem/src/Plugin/EmptyDir.php | 34 + .../flysystem/src/Plugin/ForcedCopy.php | 44 + .../flysystem/src/Plugin/ForcedRename.php | 44 + .../flysystem/src/Plugin/GetWithMetadata.php | 51 + .../league/flysystem/src/Plugin/ListFiles.php | 35 + .../league/flysystem/src/Plugin/ListPaths.php | 36 + .../league/flysystem/src/Plugin/ListWith.php | 60 + .../flysystem/src/Plugin/PluggableTrait.php | 97 + .../src/Plugin/PluginNotFoundException.php | 10 + .../league/flysystem/src/PluginInterface.php | 20 + vendor/league/flysystem/src/ReadInterface.php | 88 + .../flysystem/src/RootViolationException.php | 10 + vendor/league/flysystem/src/SafeStorage.php | 39 + .../flysystem/src/UnreadableFileException.php | 18 + vendor/league/flysystem/src/Util.php | 349 + .../src/Util/ContentListingFormatter.php | 122 + vendor/league/flysystem/src/Util/MimeType.php | 245 + .../flysystem/src/Util/StreamHasher.php | 36 + vendor/mtdowling/jmespath.php/.gitignore | 5 + vendor/mtdowling/jmespath.php/.travis.yml | 31 + vendor/mtdowling/jmespath.php/CHANGELOG.md | 58 + vendor/mtdowling/jmespath.php/LICENSE | 19 + vendor/mtdowling/jmespath.php/Makefile | 19 + vendor/mtdowling/jmespath.php/README.rst | 123 + vendor/mtdowling/jmespath.php/bin/jp.php | 74 + vendor/mtdowling/jmespath.php/bin/perf.php | 68 + vendor/mtdowling/jmespath.php/composer.json | 39 + .../mtdowling/jmespath.php/phpunit.xml.dist | 17 + .../mtdowling/jmespath.php/src/AstRuntime.php | 47 + .../jmespath.php/src/CompilerRuntime.php | 83 + .../jmespath.php/src/DebugRuntime.php | 109 + vendor/mtdowling/jmespath.php/src/Env.php | 91 + .../jmespath.php/src/FnDispatcher.php | 407 + .../mtdowling/jmespath.php/src/JmesPath.php | 17 + vendor/mtdowling/jmespath.php/src/Lexer.php | 444 + vendor/mtdowling/jmespath.php/src/Parser.php | 519 + .../jmespath.php/src/SyntaxErrorException.php | 36 + .../jmespath.php/src/TreeCompiler.php | 419 + .../jmespath.php/src/TreeInterpreter.php | 235 + vendor/mtdowling/jmespath.php/src/Utils.php | 233 + .../jmespath.php/tests/ComplianceTest.php | 138 + .../mtdowling/jmespath.php/tests/EnvTest.php | 32 + .../jmespath.php/tests/FnDispatcherTest.php | 42 + .../jmespath.php/tests/LexerTest.php | 89 + .../jmespath.php/tests/ParserTest.php | 51 + .../tests/SyntaxErrorExceptionTest.php | 43 + .../jmespath.php/tests/TreeCompilerTest.php | 24 + .../tests/TreeInterpreterTest.php | 72 + .../jmespath.php/tests/UtilsTest.php | 131 + .../jmespath.php/tests/compliance/basic.json | 112 + .../tests/compliance/boolean.json | 275 + .../tests/compliance/current.json | 25 + .../jmespath.php/tests/compliance/escape.json | 46 + .../tests/compliance/filters.json | 512 + .../tests/compliance/functions.json | 837 ++ .../tests/compliance/identifiers.json | 1377 ++ .../tests/compliance/indices.json | 346 + .../tests/compliance/literal.json | 200 + .../tests/compliance/multiselect.json | 398 + .../tests/compliance/perf/basic.json | 27 + .../tests/compliance/perf/deep_hierarchy.json | 27 + .../compliance/perf/deep_projection.json | 12 + .../tests/compliance/perf/functions.json | 17 + .../tests/compliance/perf/multiwildcard.json | 22 + .../tests/compliance/perf/wildcardindex.json | 17 + .../jmespath.php/tests/compliance/pipe.json | 131 + .../jmespath.php/tests/compliance/slice.json | 187 + .../jmespath.php/tests/compliance/syntax.json | 692 + .../tests/compliance/unicode.json | 38 + .../tests/compliance/wildcard.json | 460 + vendor/nette/php-generator/composer.json | 34 + vendor/nette/php-generator/contributing.md | 33 + vendor/nette/php-generator/license.md | 60 + vendor/nette/php-generator/readme.md | 566 + .../src/PhpGenerator/ClassType.php | 510 + .../src/PhpGenerator/Closure.php | 73 + .../src/PhpGenerator/Constant.php | 43 + .../php-generator/src/PhpGenerator/Dumper.php | 229 + .../src/PhpGenerator/Factory.php | 140 + .../src/PhpGenerator/GlobalFunction.php | 47 + .../src/PhpGenerator/Helpers.php | 100 + .../php-generator/src/PhpGenerator/Method.php | 137 + .../src/PhpGenerator/Parameter.php | 137 + .../src/PhpGenerator/PhpFile.php | 125 + .../src/PhpGenerator/PhpLiteral.php | 32 + .../src/PhpGenerator/PhpNamespace.php | 209 + .../src/PhpGenerator/Printer.php | 275 + .../src/PhpGenerator/Property.php | 121 + .../src/PhpGenerator/PsrPrinter.php | 23 + .../src/PhpGenerator/Traits/CommentAware.php | 46 + .../src/PhpGenerator/Traits/FunctionLike.php | 196 + .../src/PhpGenerator/Traits/NameAware.php | 49 + .../PhpGenerator/Traits/VisibilityAware.php | 43 + vendor/nette/utils/.phpstorm.meta.php | 19 + vendor/nette/utils/composer.json | 42 + vendor/nette/utils/contributing.md | 33 + vendor/nette/utils/license.md | 60 + vendor/nette/utils/readme.md | 41 + .../utils/src/Iterators/CachingIterator.php | 166 + vendor/nette/utils/src/Iterators/Mapper.php | 34 + vendor/nette/utils/src/Utils/ArrayHash.php | 94 + vendor/nette/utils/src/Utils/ArrayList.php | 110 + vendor/nette/utils/src/Utils/Arrays.php | 300 + vendor/nette/utils/src/Utils/Callback.php | 164 + vendor/nette/utils/src/Utils/DateTime.php | 124 + vendor/nette/utils/src/Utils/FileSystem.php | 159 + vendor/nette/utils/src/Utils/Html.php | 614 + vendor/nette/utils/src/Utils/IHtmlString.php | 20 + vendor/nette/utils/src/Utils/ITranslator.php | 23 + vendor/nette/utils/src/Utils/Image.php | 623 + vendor/nette/utils/src/Utils/Json.php | 60 + .../nette/utils/src/Utils/ObjectHelpers.php | 186 + vendor/nette/utils/src/Utils/ObjectMixin.php | 43 + vendor/nette/utils/src/Utils/Paginator.php | 212 + vendor/nette/utils/src/Utils/Random.php | 44 + vendor/nette/utils/src/Utils/Reflection.php | 303 + vendor/nette/utils/src/Utils/SmartObject.php | 121 + vendor/nette/utils/src/Utils/StaticClass.php | 36 + vendor/nette/utils/src/Utils/Strings.php | 508 + vendor/nette/utils/src/Utils/Validators.php | 344 + vendor/nette/utils/src/Utils/exceptions.php | 159 + vendor/opis/closure/CHANGELOG.md | 220 + vendor/opis/closure/LICENSE | 20 + vendor/opis/closure/NOTICE | 9 + vendor/opis/closure/README.md | 98 + vendor/opis/closure/autoload.php | 39 + vendor/opis/closure/composer.json | 40 + vendor/opis/closure/functions.php | 38 + vendor/opis/closure/src/Analyzer.php | 59 + vendor/opis/closure/src/ClosureContext.php | 34 + vendor/opis/closure/src/ClosureScope.php | 25 + vendor/opis/closure/src/ClosureStream.php | 99 + vendor/opis/closure/src/ISecurityProvider.php | 25 + vendor/opis/closure/src/ReflectionClosure.php | 1014 ++ vendor/opis/closure/src/SecurityException.php | 18 + vendor/opis/closure/src/SecurityProvider.php | 42 + vendor/opis/closure/src/SelfReference.php | 31 + .../opis/closure/src/SerializableClosure.php | 668 + 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 + vendor/predis/predis/CHANGELOG.md | 985 ++ vendor/predis/predis/CONTRIBUTING.md | 44 + vendor/predis/predis/FAQ.md | 177 + vendor/predis/predis/LICENSE | 22 + vendor/predis/predis/README.md | 492 + vendor/predis/predis/VERSION | 1 + vendor/predis/predis/autoload.php | 14 + vendor/predis/predis/bin/create-command-test | 275 + vendor/predis/predis/bin/create-pear | 233 + vendor/predis/predis/bin/create-phar | 71 + vendor/predis/predis/bin/create-single-file | 662 + vendor/predis/predis/composer.json | 31 + .../examples/custom_cluster_distributor.php | 117 + .../predis/examples/debuggable_connection.php | 92 + .../predis/examples/dispatcher_loop.php | 79 + .../examples/executing_redis_commands.php | 57 + .../predis/predis/examples/key_prefixing.php | 36 + .../examples/lua_scripting_abstraction.php | 71 + .../predis/examples/monitor_consumer.php | 44 + .../predis/examples/pipelining_commands.php | 45 + .../predis/examples/pubsub_consumer.php | 59 + .../examples/redis_collections_iterators.php | 99 + .../predis/examples/replication_complex.php | 85 + .../predis/examples/replication_sentinel.php | 58 + .../predis/examples/replication_simple.php | 52 + .../predis/examples/session_handler.php | 52 + vendor/predis/predis/examples/shared.php | 44 + .../predis/examples/transaction_using_cas.php | 52 + vendor/predis/predis/package.ini | 36 + vendor/predis/predis/src/Autoloader.php | 62 + vendor/predis/predis/src/Client.php | 547 + .../predis/src/ClientContextInterface.php | 198 + vendor/predis/predis/src/ClientException.php | 21 + vendor/predis/predis/src/ClientInterface.php | 239 + .../predis/src/Cluster/ClusterStrategy.php | 469 + .../Distributor/DistributorInterface.php | 82 + .../Distributor/EmptyRingException.php | 21 + .../src/Cluster/Distributor/HashRing.php | 270 + .../src/Cluster/Distributor/KetamaRing.php | 71 + .../predis/predis/src/Cluster/Hash/CRC16.php | 72 + .../Cluster/Hash/HashGeneratorInterface.php | 30 + .../predis/src/Cluster/PredisStrategy.php | 79 + .../predis/src/Cluster/RedisStrategy.php | 58 + .../predis/src/Cluster/StrategyInterface.php | 53 + .../Iterator/CursorBasedIterator.php | 191 + .../src/Collection/Iterator/HashKey.php | 60 + .../src/Collection/Iterator/Keyspace.php | 43 + .../src/Collection/Iterator/ListKey.php | 176 + .../predis/src/Collection/Iterator/SetKey.php | 47 + .../src/Collection/Iterator/SortedSetKey.php | 60 + vendor/predis/predis/src/Command/Command.php | 129 + .../predis/src/Command/CommandInterface.php | 81 + .../predis/src/Command/ConnectionAuth.php | 28 + .../predis/src/Command/ConnectionEcho.php | 28 + .../predis/src/Command/ConnectionPing.php | 28 + .../predis/src/Command/ConnectionQuit.php | 28 + .../predis/src/Command/ConnectionSelect.php | 28 + .../predis/src/Command/GeospatialGeoAdd.php | 42 + .../predis/src/Command/GeospatialGeoDist.php | 28 + .../predis/src/Command/GeospatialGeoHash.php | 41 + .../predis/src/Command/GeospatialGeoPos.php | 41 + .../src/Command/GeospatialGeoRadius.php | 71 + .../Command/GeospatialGeoRadiusByMember.php | 28 + .../predis/predis/src/Command/HashDelete.php | 36 + .../predis/predis/src/Command/HashExists.php | 28 + vendor/predis/predis/src/Command/HashGet.php | 28 + .../predis/predis/src/Command/HashGetAll.php | 42 + .../predis/src/Command/HashGetMultiple.php | 36 + .../predis/src/Command/HashIncrementBy.php | 28 + .../src/Command/HashIncrementByFloat.php | 28 + vendor/predis/predis/src/Command/HashKeys.php | 28 + .../predis/predis/src/Command/HashLength.php | 28 + vendor/predis/predis/src/Command/HashScan.php | 85 + vendor/predis/predis/src/Command/HashSet.php | 28 + .../predis/src/Command/HashSetMultiple.php | 48 + .../predis/src/Command/HashSetPreserve.php | 28 + .../predis/src/Command/HashStringLength.php | 28 + .../predis/predis/src/Command/HashValues.php | 28 + .../predis/src/Command/HyperLogLogAdd.php | 36 + .../predis/src/Command/HyperLogLogCount.php | 36 + .../predis/src/Command/HyperLogLogMerge.php | 36 + .../predis/predis/src/Command/KeyDelete.php | 36 + vendor/predis/predis/src/Command/KeyDump.php | 28 + .../predis/predis/src/Command/KeyExists.php | 28 + .../predis/predis/src/Command/KeyExpire.php | 28 + .../predis/predis/src/Command/KeyExpireAt.php | 28 + vendor/predis/predis/src/Command/KeyKeys.php | 28 + .../predis/predis/src/Command/KeyMigrate.php | 50 + vendor/predis/predis/src/Command/KeyMove.php | 28 + .../predis/predis/src/Command/KeyPersist.php | 28 + .../predis/src/Command/KeyPreciseExpire.php | 28 + .../predis/src/Command/KeyPreciseExpireAt.php | 28 + .../src/Command/KeyPreciseTimeToLive.php | 28 + .../predis/predis/src/Command/KeyRandom.php | 36 + .../predis/predis/src/Command/KeyRename.php | 28 + .../predis/src/Command/KeyRenamePreserve.php | 28 + .../predis/predis/src/Command/KeyRestore.php | 28 + vendor/predis/predis/src/Command/KeyScan.php | 66 + vendor/predis/predis/src/Command/KeySort.php | 83 + .../predis/src/Command/KeyTimeToLive.php | 28 + vendor/predis/predis/src/Command/KeyType.php | 28 + .../predis/predis/src/Command/ListIndex.php | 28 + .../predis/predis/src/Command/ListInsert.php | 28 + .../predis/predis/src/Command/ListLength.php | 28 + .../predis/src/Command/ListPopFirst.php | 28 + .../src/Command/ListPopFirstBlocking.php | 41 + .../predis/predis/src/Command/ListPopLast.php | 28 + .../src/Command/ListPopLastBlocking.php | 28 + .../src/Command/ListPopLastPushHead.php | 28 + .../Command/ListPopLastPushHeadBlocking.php | 28 + .../predis/src/Command/ListPushHead.php | 28 + .../predis/src/Command/ListPushHeadX.php | 28 + .../predis/src/Command/ListPushTail.php | 36 + .../predis/src/Command/ListPushTailX.php | 28 + .../predis/predis/src/Command/ListRange.php | 28 + .../predis/predis/src/Command/ListRemove.php | 28 + vendor/predis/predis/src/Command/ListSet.php | 28 + vendor/predis/predis/src/Command/ListTrim.php | 28 + .../Command/PrefixableCommandInterface.php | 27 + .../Command/Processor/KeyPrefixProcessor.php | 450 + .../src/Command/Processor/ProcessorChain.php | 130 + .../Command/Processor/ProcessorInterface.php | 29 + .../predis/src/Command/PubSubPublish.php | 28 + .../predis/src/Command/PubSubPubsub.php | 61 + .../predis/src/Command/PubSubSubscribe.php | 36 + .../src/Command/PubSubSubscribeByPattern.php | 28 + .../predis/src/Command/PubSubUnsubscribe.php | 36 + .../Command/PubSubUnsubscribeByPattern.php | 28 + .../predis/predis/src/Command/RawCommand.php | 131 + .../predis/src/Command/ScriptCommand.php | 77 + .../Command/ServerBackgroundRewriteAOF.php | 36 + .../src/Command/ServerBackgroundSave.php | 36 + .../predis/src/Command/ServerClient.php | 74 + .../predis/src/Command/ServerCommand.php | 28 + .../predis/src/Command/ServerConfig.php | 49 + .../predis/src/Command/ServerDatabaseSize.php | 28 + .../predis/predis/src/Command/ServerEval.php | 38 + .../predis/src/Command/ServerEvalSHA.php | 38 + .../predis/src/Command/ServerFlushAll.php | 28 + .../src/Command/ServerFlushDatabase.php | 28 + .../predis/predis/src/Command/ServerInfo.php | 111 + .../predis/src/Command/ServerInfoV26x.php | 56 + .../predis/src/Command/ServerLastSave.php | 28 + .../predis/src/Command/ServerMonitor.php | 28 + .../predis/src/Command/ServerObject.php | 28 + .../predis/predis/src/Command/ServerSave.php | 28 + .../predis/src/Command/ServerScript.php | 28 + .../predis/src/Command/ServerSentinel.php | 66 + .../predis/src/Command/ServerShutdown.php | 28 + .../predis/src/Command/ServerSlaveOf.php | 40 + .../predis/src/Command/ServerSlowlog.php | 51 + .../predis/predis/src/Command/ServerTime.php | 28 + vendor/predis/predis/src/Command/SetAdd.php | 36 + .../predis/src/Command/SetCardinality.php | 28 + .../predis/src/Command/SetDifference.php | 28 + .../predis/src/Command/SetDifferenceStore.php | 28 + .../predis/src/Command/SetIntersection.php | 36 + .../src/Command/SetIntersectionStore.php | 40 + .../predis/predis/src/Command/SetIsMember.php | 28 + .../predis/predis/src/Command/SetMembers.php | 28 + vendor/predis/predis/src/Command/SetMove.php | 28 + vendor/predis/predis/src/Command/SetPop.php | 28 + .../predis/src/Command/SetRandomMember.php | 28 + .../predis/predis/src/Command/SetRemove.php | 36 + vendor/predis/predis/src/Command/SetScan.php | 66 + vendor/predis/predis/src/Command/SetUnion.php | 28 + .../predis/src/Command/SetUnionStore.php | 28 + .../predis/src/Command/StringAppend.php | 28 + .../predis/src/Command/StringBitCount.php | 28 + .../predis/src/Command/StringBitField.php | 28 + .../predis/predis/src/Command/StringBitOp.php | 42 + .../predis/src/Command/StringBitPos.php | 28 + .../predis/src/Command/StringDecrement.php | 28 + .../predis/src/Command/StringDecrementBy.php | 28 + .../predis/predis/src/Command/StringGet.php | 28 + .../predis/src/Command/StringGetBit.php | 28 + .../predis/src/Command/StringGetMultiple.php | 36 + .../predis/src/Command/StringGetRange.php | 28 + .../predis/src/Command/StringGetSet.php | 28 + .../predis/src/Command/StringIncrement.php | 28 + .../predis/src/Command/StringIncrementBy.php | 28 + .../src/Command/StringIncrementByFloat.php | 28 + .../src/Command/StringPreciseSetExpire.php | 28 + .../predis/predis/src/Command/StringSet.php | 28 + .../predis/src/Command/StringSetBit.php | 28 + .../predis/src/Command/StringSetExpire.php | 28 + .../predis/src/Command/StringSetMultiple.php | 48 + .../src/Command/StringSetMultiplePreserve.php | 28 + .../predis/src/Command/StringSetPreserve.php | 28 + .../predis/src/Command/StringSetRange.php | 28 + .../predis/src/Command/StringStrlen.php | 28 + .../predis/src/Command/StringSubstr.php | 28 + .../predis/src/Command/TransactionDiscard.php | 28 + .../predis/src/Command/TransactionExec.php | 28 + .../predis/src/Command/TransactionMulti.php | 28 + .../predis/src/Command/TransactionUnwatch.php | 28 + .../predis/src/Command/TransactionWatch.php | 40 + vendor/predis/predis/src/Command/ZSetAdd.php | 43 + .../predis/src/Command/ZSetCardinality.php | 28 + .../predis/predis/src/Command/ZSetCount.php | 28 + .../predis/src/Command/ZSetIncrementBy.php | 28 + .../src/Command/ZSetIntersectionStore.php | 28 + .../predis/src/Command/ZSetLexCount.php | 28 + .../predis/predis/src/Command/ZSetRange.php | 105 + .../predis/src/Command/ZSetRangeByLex.php | 55 + .../predis/src/Command/ZSetRangeByScore.php | 68 + vendor/predis/predis/src/Command/ZSetRank.php | 28 + .../predis/predis/src/Command/ZSetRemove.php | 36 + .../src/Command/ZSetRemoveRangeByLex.php | 28 + .../src/Command/ZSetRemoveRangeByRank.php | 28 + .../src/Command/ZSetRemoveRangeByScore.php | 28 + .../predis/src/Command/ZSetReverseRange.php | 28 + .../src/Command/ZSetReverseRangeByLex.php | 28 + .../src/Command/ZSetReverseRangeByScore.php | 28 + .../predis/src/Command/ZSetReverseRank.php | 28 + vendor/predis/predis/src/Command/ZSetScan.php | 85 + .../predis/predis/src/Command/ZSetScore.php | 28 + .../predis/src/Command/ZSetUnionStore.php | 78 + .../predis/src/CommunicationException.php | 80 + .../src/Configuration/ClusterOption.php | 76 + .../Configuration/ConnectionFactoryOption.php | 60 + .../src/Configuration/ExceptionsOption.php | 37 + .../src/Configuration/OptionInterface.php | 40 + .../predis/src/Configuration/Options.php | 122 + .../src/Configuration/OptionsInterface.php | 64 + .../predis/src/Configuration/PrefixOption.php | 44 + .../src/Configuration/ProfileOption.php | 69 + .../src/Configuration/ReplicationOption.php | 75 + .../src/Connection/AbstractConnection.php | 226 + .../Connection/Aggregate/ClusterInterface.php | 24 + .../Aggregate/MasterSlaveReplication.php | 509 + .../Connection/Aggregate/PredisCluster.php | 235 + .../src/Connection/Aggregate/RedisCluster.php | 673 + .../Aggregate/ReplicationInterface.php | 52 + .../Aggregate/SentinelReplication.php | 720 + .../AggregateConnectionInterface.php | 57 + .../CompositeConnectionInterface.php | 49 + .../Connection/CompositeStreamConnection.php | 125 + .../src/Connection/ConnectionException.php | 23 + .../src/Connection/ConnectionInterface.php | 66 + .../predis/predis/src/Connection/Factory.php | 188 + .../src/Connection/FactoryInterface.php | 52 + .../Connection/NodeConnectionInterface.php | 58 + .../predis/src/Connection/Parameters.php | 176 + .../src/Connection/ParametersInterface.php | 62 + .../Connection/PhpiredisSocketConnection.php | 418 + .../Connection/PhpiredisStreamConnection.php | 238 + .../src/Connection/StreamConnection.php | 396 + .../src/Connection/WebdisConnection.php | 366 + vendor/predis/predis/src/Monitor/Consumer.php | 173 + .../predis/src/NotSupportedException.php | 22 + vendor/predis/predis/src/Pipeline/Atomic.php | 119 + .../src/Pipeline/ConnectionErrorProof.php | 130 + .../predis/src/Pipeline/FireAndForget.php | 36 + .../predis/predis/src/Pipeline/Pipeline.php | 247 + vendor/predis/predis/src/PredisException.php | 21 + vendor/predis/predis/src/Profile/Factory.php | 101 + .../predis/src/Profile/ProfileInterface.php | 59 + .../predis/src/Profile/RedisProfile.php | 146 + .../predis/src/Profile/RedisUnstable.php | 38 + .../predis/src/Profile/RedisVersion200.php | 173 + .../predis/src/Profile/RedisVersion220.php | 202 + .../predis/src/Profile/RedisVersion240.php | 207 + .../predis/src/Profile/RedisVersion260.php | 235 + .../predis/src/Profile/RedisVersion280.php | 267 + .../predis/src/Profile/RedisVersion300.php | 270 + .../predis/src/Profile/RedisVersion320.php | 281 + .../predis/src/Protocol/ProtocolException.php | 24 + .../Protocol/ProtocolProcessorInterface.php | 41 + .../Protocol/RequestSerializerInterface.php | 31 + .../src/Protocol/ResponseReaderInterface.php | 32 + .../Text/CompositeProtocolProcessor.php | 107 + .../Protocol/Text/Handler/BulkResponse.php | 55 + .../Protocol/Text/Handler/ErrorResponse.php | 34 + .../Protocol/Text/Handler/IntegerResponse.php | 46 + .../Text/Handler/MultiBulkResponse.php | 68 + .../Text/Handler/ResponseHandlerInterface.php | 33 + .../Protocol/Text/Handler/StatusResponse.php | 35 + .../Handler/StreamableMultiBulkResponse.php | 47 + .../src/Protocol/Text/ProtocolProcessor.php | 123 + .../src/Protocol/Text/RequestSerializer.php | 46 + .../src/Protocol/Text/ResponseReader.php | 116 + .../predis/src/PubSub/AbstractConsumer.php | 219 + vendor/predis/predis/src/PubSub/Consumer.php | 158 + .../predis/src/PubSub/DispatcherLoop.php | 170 + .../Replication/MissingMasterException.php | 23 + .../src/Replication/ReplicationStrategy.php | 304 + .../predis/src/Replication/RoleException.php | 24 + vendor/predis/predis/src/Response/Error.php | 59 + .../predis/src/Response/ErrorInterface.php | 35 + .../src/Response/Iterator/MultiBulk.php | 77 + .../Response/Iterator/MultiBulkIterator.php | 104 + .../src/Response/Iterator/MultiBulkTuple.php | 90 + .../predis/src/Response/ResponseInterface.php | 21 + .../predis/src/Response/ServerException.php | 44 + vendor/predis/predis/src/Response/Status.php | 79 + vendor/predis/predis/src/Session/Handler.php | 142 + .../Transaction/AbortedMultiExecException.php | 45 + .../predis/src/Transaction/MultiExec.php | 461 + .../predis/src/Transaction/MultiExecState.php | 166 + 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/.gitignore | 1 + 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 + .../log/Psr/Log/Test/LoggerInterfaceTest.php | 146 + 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/ramsey/uuid/CHANGELOG.md | 643 + vendor/ramsey/uuid/LICENSE | 21 + vendor/ramsey/uuid/README.md | 185 + vendor/ramsey/uuid/composer.json | 92 + vendor/ramsey/uuid/src/BinaryUtils.php | 41 + .../uuid/src/Builder/DefaultUuidBuilder.php | 54 + .../uuid/src/Builder/DegradedUuidBuilder.php | 53 + .../uuid/src/Builder/UuidBuilderInterface.php | 34 + .../ramsey/uuid/src/Codec/CodecInterface.php | 60 + .../ramsey/uuid/src/Codec/GuidStringCodec.php | 103 + .../uuid/src/Codec/OrderedTimeCodec.php | 68 + vendor/ramsey/uuid/src/Codec/StringCodec.php | 170 + .../src/Codec/TimestampFirstCombCodec.php | 108 + .../uuid/src/Codec/TimestampLastCombCodec.php | 22 + .../Converter/Number/BigNumberConverter.php | 54 + .../Number/DegradedNumberConverter.php | 58 + .../Converter/NumberConverterInterface.php | 48 + .../Converter/Time/BigNumberTimeConverter.php | 59 + .../Converter/Time/DegradedTimeConverter.php | 42 + .../src/Converter/Time/PhpTimeConverter.php | 47 + .../src/Converter/TimeConverterInterface.php | 37 + vendor/ramsey/uuid/src/DegradedUuid.php | 116 + .../Exception/InvalidUuidStringException.php | 24 + .../UnsatisfiedDependencyException.php | 25 + .../UnsupportedOperationException.php | 24 + vendor/ramsey/uuid/src/FeatureSet.php | 335 + .../uuid/src/Generator/CombGenerator.php | 91 + .../src/Generator/DefaultTimeGenerator.php | 141 + .../uuid/src/Generator/MtRandGenerator.php | 45 + .../uuid/src/Generator/OpenSslGenerator.php | 43 + .../src/Generator/PeclUuidRandomGenerator.php | 37 + .../src/Generator/PeclUuidTimeGenerator.php | 38 + .../src/Generator/RandomBytesGenerator.php | 39 + .../src/Generator/RandomGeneratorFactory.php | 31 + .../Generator/RandomGeneratorInterface.php | 37 + .../uuid/src/Generator/RandomLibAdapter.php | 62 + .../src/Generator/SodiumRandomGenerator.php | 41 + .../src/Generator/TimeGeneratorFactory.php | 72 + .../src/Generator/TimeGeneratorInterface.php | 43 + .../Provider/Node/FallbackNodeProvider.php | 59 + .../src/Provider/Node/RandomNodeProvider.php | 57 + .../src/Provider/Node/SystemNodeProvider.php | 128 + .../src/Provider/NodeProviderInterface.php | 32 + .../src/Provider/Time/FixedTimeProvider.php | 77 + .../src/Provider/Time/SystemTimeProvider.php | 33 + .../src/Provider/TimeProviderInterface.php | 29 + vendor/ramsey/uuid/src/Uuid.php | 751 + vendor/ramsey/uuid/src/UuidFactory.php | 315 + .../ramsey/uuid/src/UuidFactoryInterface.php | 108 + vendor/ramsey/uuid/src/UuidInterface.php | 274 + vendor/ramsey/uuid/src/functions.php | 78 + vendor/services.php | 7 + vendor/swoole/ide-helper/.gitignore | 4 + vendor/swoole/ide-helper/.travis.yml | 10 + vendor/swoole/ide-helper/LICENSE | 202 + vendor/swoole/ide-helper/README.md | 47 + vendor/swoole/ide-helper/bin/generator.php | 31 + vendor/swoole/ide-helper/bin/generator.sh | 42 + vendor/swoole/ide-helper/composer.json | 22 + .../config/chinese/server/method/send.php | 27 + .../ide-helper/output/swoole/aliases.php | 50 + .../ide-helper/output/swoole/constants.php | 360 + .../ide-helper/output/swoole/functions.php | 244 + .../output/swoole/namespace/Atomic.php | 62 + .../output/swoole/namespace/Atomic/Long.php | 48 + .../output/swoole/namespace/Buffer.php | 77 + .../output/swoole/namespace/Client.php | 150 + .../swoole/namespace/Connection/Iterator.php | 87 + .../output/swoole/namespace/Coroutine.php | 205 + .../swoole/namespace/Coroutine/Channel.php | 66 + .../swoole/namespace/Coroutine/Client.php | 151 + .../swoole/namespace/Coroutine/Context.php | 13 + .../namespace/Coroutine/Http/Client.php | 214 + .../Coroutine/Http/Client/Exception.php | 9 + .../namespace/Coroutine/Http/Server.php | 66 + .../namespace/Coroutine/Http2/Client.php | 105 + .../Coroutine/Http2/Client/Exception.php | 9 + .../swoole/namespace/Coroutine/Iterator.php | 13 + .../swoole/namespace/Coroutine/MySQL.php | 133 + .../namespace/Coroutine/MySQL/Exception.php | 9 + .../namespace/Coroutine/MySQL/Statement.php | 61 + .../swoole/namespace/Coroutine/Redis.php | 1174 ++ .../swoole/namespace/Coroutine/Scheduler.php | 39 + .../swoole/namespace/Coroutine/Socket.php | 159 + .../namespace/Coroutine/Socket/Exception.php | 9 + .../swoole/namespace/Coroutine/System.php | 86 + .../output/swoole/namespace/Error.php | 9 + .../output/swoole/namespace/Event.php | 86 + .../output/swoole/namespace/Exception.php | 9 + .../output/swoole/namespace/ExitException.php | 27 + .../output/swoole/namespace/Http/Request.php | 45 + .../output/swoole/namespace/Http/Response.php | 163 + .../output/swoole/namespace/Http/Server.php | 11 + .../output/swoole/namespace/Http2/Request.php | 21 + .../swoole/namespace/Http2/Response.php | 25 + .../output/swoole/namespace/Lock.php | 78 + .../output/swoole/namespace/Process.php | 193 + .../output/swoole/namespace/Process/Pool.php | 70 + .../output/swoole/namespace/Redis/Server.php | 51 + .../output/swoole/namespace/Runtime.php | 30 + .../output/swoole/namespace/Server.php | 315 + .../output/swoole/namespace/Server/Port.php | 77 + .../output/swoole/namespace/Server/Task.php | 31 + .../output/swoole/namespace/Table.php | 166 + .../output/swoole/namespace/Table/Row.php | 45 + .../output/swoole/namespace/Timer.php | 72 + .../swoole/namespace/Timer/Iterator.php | 13 + .../swoole/namespace/WebSocket/CloseFrame.php | 15 + .../swoole/namespace/WebSocket/Frame.php | 40 + .../swoole/namespace/WebSocket/Server.php | 46 + .../output/swoole_async/aliases.php | 14 + .../output/swoole_async/functions.php | 43 + .../output/swoole_async/namespace/Async.php | 58 + .../swoole_async/namespace/Async/Client.php | 186 + .../namespace/Async/http/client.php | 162 + .../swoole_async/namespace/Async/mysql.php | 114 + .../swoole_async/namespace/Async/redis.php | 80 + .../output/swoole_async/namespace/Channel.php | 45 + .../swoole_async/namespace/Http/Client.php | 162 + .../swoole_async/namespace/Memory/Pool.php | 34 + .../namespace/Memory/Pool/Slice.php | 27 + .../output/swoole_async/namespace/Mmap.php | 16 + .../swoole_async/namespace/MsgQueue.php | 52 + .../output/swoole_async/namespace/MySQL.php | 114 + .../namespace/MySQL/Exception.php | 9 + .../output/swoole_async/namespace/Redis.php | 80 + .../swoole_async/namespace/RingQueue.php | 52 + .../ide-helper/output/swoole_lib/alias.php | 5 + .../ide-helper/output/swoole_lib/alias_ns.php | 23 + .../output/swoole_lib/constants.php | 6 + .../output/swoole_lib/core/ArrayObject.php | 665 + .../output/swoole_lib/core/Constant.php | 186 + .../swoole_lib/core/Coroutine/ObjectPool.php | 76 + .../swoole_lib/core/Coroutine/Server.php | 125 + .../core/Coroutine/Server/Connection.php | 30 + .../swoole_lib/core/Coroutine/WaitGroup.php | 53 + .../output/swoole_lib/core/Curl/Exception.php | 10 + .../output/swoole_lib/core/Curl/Handler.php | 560 + .../swoole_lib/core/Http/StatusCode.php | 142 + .../output/swoole_lib/core/StringObject.php | 237 + .../output/swoole_lib/functions.php | 49 + .../ide-helper/output/swoole_orm/aliases.php | 3 + .../output/swoole_orm/functions.php | 7 + .../output/swoole_postgresql/aliases.php | 3 + .../output/swoole_postgresql/constants.php | 5 + .../namespace/Coroutine/PostgreSQL.php | 103 + .../output/swoole_serialize/aliases.php | 3 + .../output/swoole_serialize/constants.php | 5 + .../swoole_serialize/namespace/Serialize.php | 23 + .../output/swoole_zookeeper/aliases.php | 2 + .../output/swoole_zookeeper/constants.php | 5 + .../swoole_zookeeper/namespace/zookeeper.php | 157 + .../ide-helper/src/AbstractStubGenerator.php | 334 + vendor/swoole/ide-helper/src/Constant.php | 18 + vendor/swoole/ide-helper/src/Exception.php | 7 + .../ide-helper/src/Rules/AbstractRule.php | 69 + .../ide-helper/src/Rules/NamespaceRule.php | 36 + .../ide-helper/src/StubGenerators/Swoole.php | 24 + .../src/StubGenerators/SwooleAsync.php | 24 + .../src/StubGenerators/SwooleLib.php | 80 + .../src/StubGenerators/SwooleOrm.php | 24 + .../src/StubGenerators/SwoolePostgresql.php | 24 + .../src/StubGenerators/SwooleSerialize.php | 24 + .../src/StubGenerators/SwooleZookeeper.php | 24 + vendor/symfony/finder/.gitattributes | 3 + 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 | 808 + vendor/symfony/finder/Gitignore.php | 105 + vendor/symfony/finder/Glob.php | 116 + .../finder/Iterator/CustomFilterIterator.php | 61 + .../Iterator/DateRangeFilterIterator.php | 58 + .../Iterator/DepthRangeFilterIterator.php | 45 + .../ExcludeDirectoryFilterIterator.php | 87 + .../Iterator/FileTypeFilterIterator.php | 53 + .../Iterator/FilecontentFilterIterator.php | 58 + .../Iterator/FilenameFilterIterator.php | 47 + .../Iterator/MultiplePcreFilterIterator.php | 112 + .../finder/Iterator/PathFilterIterator.php | 56 + .../Iterator/RecursiveDirectoryIterator.php | 140 + .../Iterator/SizeRangeFilterIterator.php | 57 + .../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 | 33 + vendor/symfony/polyfill-ctype/Ctype.php | 227 + vendor/symfony/polyfill-ctype/LICENSE | 19 + vendor/symfony/polyfill-ctype/README.md | 12 + vendor/symfony/polyfill-ctype/bootstrap.php | 26 + vendor/symfony/polyfill-ctype/composer.json | 34 + vendor/symfony/polyfill-mbstring/LICENSE | 19 + vendor/symfony/polyfill-mbstring/Mbstring.php | 847 ++ vendor/symfony/polyfill-mbstring/README.md | 13 + .../Resources/unidata/lowerCase.php | 1096 ++ .../Resources/unidata/titleCaseRegexp.php | 5 + .../Resources/unidata/upperCase.php | 1104 ++ .../symfony/polyfill-mbstring/bootstrap.php | 62 + .../symfony/polyfill-mbstring/composer.json | 34 + vendor/topthink/framework/.gitignore | 7 + vendor/topthink/framework/.travis.yml | 34 + vendor/topthink/framework/CONTRIBUTING.md | 119 + vendor/topthink/framework/LICENSE.txt | 32 + vendor/topthink/framework/README.md | 86 + vendor/topthink/framework/composer.json | 55 + vendor/topthink/framework/logo.png | Bin 0 -> 6995 bytes vendor/topthink/framework/phpunit.xml.dist | 25 + vendor/topthink/framework/src/helper.php | 663 + vendor/topthink/framework/src/lang/zh-cn.php | 148 + vendor/topthink/framework/src/think/App.php | 611 + vendor/topthink/framework/src/think/Cache.php | 197 + .../topthink/framework/src/think/Config.php | 193 + .../topthink/framework/src/think/Console.php | 732 + .../framework/src/think/Container.php | 551 + .../topthink/framework/src/think/Cookie.php | 230 + vendor/topthink/framework/src/think/Db.php | 114 + vendor/topthink/framework/src/think/Env.php | 181 + vendor/topthink/framework/src/think/Event.php | 301 + .../framework/src/think/Exception.php | 60 + .../topthink/framework/src/think/Facade.php | 102 + vendor/topthink/framework/src/think/File.php | 187 + .../framework/src/think/Filesystem.php | 89 + vendor/topthink/framework/src/think/Http.php | 285 + vendor/topthink/framework/src/think/Lang.php | 277 + vendor/topthink/framework/src/think/Log.php | 342 + .../topthink/framework/src/think/Manager.php | 178 + .../framework/src/think/Middleware.php | 259 + .../topthink/framework/src/think/Pipeline.php | 106 + .../topthink/framework/src/think/Request.php | 2134 +++ .../topthink/framework/src/think/Response.php | 410 + vendor/topthink/framework/src/think/Route.php | 894 ++ .../topthink/framework/src/think/Service.php | 66 + .../topthink/framework/src/think/Session.php | 65 + .../topthink/framework/src/think/Validate.php | 1677 +++ vendor/topthink/framework/src/think/View.php | 187 + .../framework/src/think/cache/Driver.php | 347 + .../framework/src/think/cache/TagSet.php | 130 + .../framework/src/think/cache/driver/File.php | 304 + .../src/think/cache/driver/Memcache.php | 209 + .../src/think/cache/driver/Memcached.php | 221 + .../src/think/cache/driver/Redis.php | 250 + .../src/think/cache/driver/Wincache.php | 175 + .../framework/src/think/console/Command.php | 504 + .../framework/src/think/console/Input.php | 465 + .../framework/src/think/console/LICENSE | 19 + .../framework/src/think/console/Output.php | 224 + .../framework/src/think/console/Table.php | 300 + .../framework/src/think/console/bin/README.md | 1 + .../src/think/console/bin/hiddeninput.exe | Bin 0 -> 9216 bytes .../src/think/console/command/Clear.php | 64 + .../src/think/console/command/Help.php | 69 + .../src/think/console/command/Lists.php | 73 + .../src/think/console/command/Make.php | 99 + .../src/think/console/command/RouteList.php | 129 + .../src/think/console/command/RunServer.php | 57 + .../think/console/command/ServiceDiscover.php | 48 + .../think/console/command/VendorPublish.php | 66 + .../src/think/console/command/Version.php | 33 + .../think/console/command/make/Command.php | 55 + .../think/console/command/make/Controller.php | 56 + .../src/think/console/command/make/Event.php | 35 + .../think/console/command/make/Listener.php | 35 + .../think/console/command/make/Middleware.php | 36 + .../src/think/console/command/make/Model.php | 36 + .../think/console/command/make/Service.php | 36 + .../think/console/command/make/Subscribe.php | 35 + .../think/console/command/make/Validate.php | 39 + .../console/command/make/stubs/command.stub | 26 + .../command/make/stubs/controller.api.stub | 64 + .../command/make/stubs/controller.plain.stub | 9 + .../command/make/stubs/controller.stub | 85 + .../console/command/make/stubs/event.stub | 8 + .../console/command/make/stubs/listener.stub | 17 + .../command/make/stubs/middleware.stub | 19 + .../console/command/make/stubs/model.stub | 14 + .../console/command/make/stubs/service.stub | 29 + .../console/command/make/stubs/subscribe.stub | 8 + .../console/command/make/stubs/validate.stub | 25 + .../think/console/command/optimize/Route.php | 67 + .../think/console/command/optimize/Schema.php | 116 + .../src/think/console/input/Argument.php | 115 + .../src/think/console/input/Definition.php | 375 + .../src/think/console/input/Option.php | 190 + .../src/think/console/output/Ask.php | 336 + .../src/think/console/output/Descriptor.php | 319 + .../src/think/console/output/Formatter.php | 198 + .../src/think/console/output/Question.php | 211 + .../console/output/descriptor/Console.php | 153 + .../think/console/output/driver/Buffer.php | 52 + .../think/console/output/driver/Console.php | 368 + .../think/console/output/driver/Nothing.php | 33 + .../think/console/output/formatter/Stack.php | 116 + .../think/console/output/formatter/Style.php | 190 + .../think/console/output/question/Choice.php | 163 + .../console/output/question/Confirmation.php | 57 + .../think/contract/CacheHandlerInterface.php | 88 + .../think/contract/LogHandlerInterface.php | 28 + .../think/contract/ModelRelationInterface.php | 99 + .../contract/SessionHandlerInterface.php | 23 + .../contract/TemplateHandlerInterface.php | 61 + .../framework/src/think/event/AppInit.php | 19 + .../framework/src/think/event/HttpEnd.php | 19 + .../framework/src/think/event/HttpRun.php | 19 + .../framework/src/think/event/LogWrite.php | 31 + .../framework/src/think/event/RouteLoaded.php | 21 + .../exception/ClassNotFoundException.php | 39 + .../src/think/exception/ErrorException.php | 57 + .../src/think/exception/FileException.php | 17 + .../think/exception/FuncNotFoundException.php | 30 + .../framework/src/think/exception/Handle.php | 334 + .../src/think/exception/HttpException.php | 42 + .../think/exception/HttpResponseException.php | 37 + .../exception/InvalidArgumentException.php | 22 + .../exception/RouteNotFoundException.php | 26 + .../src/think/exception/ValidateException.php | 37 + .../framework/src/think/facade/App.php | 33 + .../framework/src/think/facade/Cache.php | 33 + .../framework/src/think/facade/Config.php | 33 + .../framework/src/think/facade/Console.php | 33 + .../framework/src/think/facade/Cookie.php | 33 + .../framework/src/think/facade/Env.php | 33 + .../framework/src/think/facade/Event.php | 33 + .../framework/src/think/facade/Filesystem.php | 28 + .../framework/src/think/facade/Lang.php | 33 + .../framework/src/think/facade/Log.php | 33 + .../framework/src/think/facade/Middleware.php | 33 + .../framework/src/think/facade/Request.php | 33 + .../framework/src/think/facade/Route.php | 33 + .../framework/src/think/facade/Session.php | 33 + .../framework/src/think/facade/Validate.php | 39 + .../framework/src/think/facade/View.php | 33 + .../framework/src/think/file/UploadedFile.php | 143 + .../src/think/filesystem/CacheStore.php | 54 + .../framework/src/think/filesystem/Driver.php | 133 + .../src/think/filesystem/driver/Local.php | 41 + .../src/think/initializer/BootService.php | 26 + .../framework/src/think/initializer/Error.php | 117 + .../src/think/initializer/RegisterService.php | 48 + .../framework/src/think/log/Channel.php | 282 + .../framework/src/think/log/ChannelSet.php | 39 + .../framework/src/think/log/driver/File.php | 205 + .../framework/src/think/log/driver/Socket.php | 306 + .../src/think/middleware/AllowCrossDomain.php | 66 + .../think/middleware/CheckRequestCache.php | 163 + .../src/think/middleware/FormTokenCheck.php | 45 + .../src/think/middleware/LoadLangPack.php | 61 + .../src/think/middleware/SessionInit.php | 80 + .../framework/src/think/response/File.php | 145 + .../framework/src/think/response/Html.php | 34 + .../framework/src/think/response/Json.php | 62 + .../framework/src/think/response/Jsonp.php | 74 + .../framework/src/think/response/Redirect.php | 98 + .../framework/src/think/response/View.php | 149 + .../framework/src/think/response/Xml.php | 127 + .../framework/src/think/route/Dispatch.php | 265 + .../framework/src/think/route/Domain.php | 183 + .../framework/src/think/route/Resource.php | 251 + .../framework/src/think/route/Rule.php | 925 ++ .../framework/src/think/route/RuleGroup.php | 541 + .../framework/src/think/route/RuleItem.php | 330 + .../framework/src/think/route/RuleName.php | 195 + .../framework/src/think/route/Url.php | 512 + .../src/think/route/dispatch/Callback.php | 30 + .../src/think/route/dispatch/Controller.php | 176 + .../src/think/route/dispatch/Redirect.php | 27 + .../src/think/route/dispatch/Response.php | 27 + .../src/think/route/dispatch/Url.php | 118 + .../src/think/route/dispatch/View.php | 28 + .../src/think/service/ModelService.php | 47 + .../src/think/service/PaginatorService.php | 52 + .../src/think/service/ValidateService.php | 31 + .../framework/src/think/session/Store.php | 340 + .../src/think/session/driver/Cache.php | 50 + .../src/think/session/driver/File.php | 249 + .../src/think/validate/ValidateRule.php | 172 + .../framework/src/think/view/driver/Php.php | 191 + .../framework/src/tpl/think_exception.tpl | 502 + vendor/topthink/framework/tests/AppTest.php | 215 + vendor/topthink/framework/tests/CacheTest.php | 149 + .../topthink/framework/tests/ConfigTest.php | 46 + .../framework/tests/ContainerTest.php | 313 + vendor/topthink/framework/tests/DbTest.php | 44 + vendor/topthink/framework/tests/EnvTest.php | 82 + vendor/topthink/framework/tests/EventTest.php | 143 + .../framework/tests/FilesystemTest.php | 131 + vendor/topthink/framework/tests/HttpTest.php | 154 + vendor/topthink/framework/tests/LogTest.php | 143 + .../framework/tests/MiddlewareTest.php | 121 + .../topthink/framework/tests/SessionTest.php | 225 + vendor/topthink/framework/tests/ViewTest.php | 127 + vendor/topthink/framework/tests/bootstrap.php | 3 + vendor/topthink/think-helper/.gitignore | 3 + vendor/topthink/think-helper/LICENSE | 201 + vendor/topthink/think-helper/README.md | 33 + vendor/topthink/think-helper/composer.json | 22 + .../topthink/think-helper/src/Collection.php | 651 + .../think-helper/src/contract/Arrayable.php | 8 + .../think-helper/src/contract/Jsonable.php | 8 + vendor/topthink/think-helper/src/helper.php | 279 + .../topthink/think-helper/src/helper/Arr.php | 634 + .../topthink/think-helper/src/helper/Str.php | 234 + vendor/topthink/think-multi-app/LICENSE | 201 + vendor/topthink/think-multi-app/README.md | 14 + vendor/topthink/think-multi-app/composer.json | 28 + .../topthink/think-multi-app/src/MultiApp.php | 268 + .../topthink/think-multi-app/src/Service.php | 29 + vendor/topthink/think-multi-app/src/Url.php | 224 + .../think-multi-app/src/command/Build.php | 180 + .../src/command/stubs/controller.stub | 12 + vendor/topthink/think-orm/.gitignore | 3 + vendor/topthink/think-orm/LICENSE | 201 + vendor/topthink/think-orm/README.md | 27 + vendor/topthink/think-orm/composer.json | 28 + vendor/topthink/think-orm/src/DbManager.php | 406 + vendor/topthink/think-orm/src/Model.php | 993 ++ vendor/topthink/think-orm/src/Paginator.php | 497 + .../topthink/think-orm/src/db/BaseQuery.php | 1270 ++ vendor/topthink/think-orm/src/db/Builder.php | 1281 ++ .../topthink/think-orm/src/db/CacheItem.php | 209 + .../topthink/think-orm/src/db/Connection.php | 275 + .../think-orm/src/db/ConnectionInterface.php | 196 + vendor/topthink/think-orm/src/db/Fetch.php | 493 + vendor/topthink/think-orm/src/db/Mongo.php | 741 + .../think-orm/src/db/PDOConnection.php | 1697 +++ vendor/topthink/think-orm/src/db/Query.php | 493 + vendor/topthink/think-orm/src/db/Raw.php | 52 + vendor/topthink/think-orm/src/db/Where.php | 182 + .../think-orm/src/db/builder/Mongo.php | 675 + .../think-orm/src/db/builder/Mysql.php | 421 + .../think-orm/src/db/builder/Oracle.php | 95 + .../think-orm/src/db/builder/Pgsql.php | 118 + .../think-orm/src/db/builder/Sqlite.php | 97 + .../think-orm/src/db/builder/Sqlsrv.php | 184 + .../src/db/concern/AggregateQuery.php | 107 + .../src/db/concern/JoinAndViewQuery.php | 229 + .../src/db/concern/ModelRelationQuery.php | 516 + .../think-orm/src/db/concern/ParamsBind.php | 106 + .../src/db/concern/ResultOperation.php | 248 + .../src/db/concern/TableFieldInfo.php | 99 + .../src/db/concern/TimeFieldQuery.php | 218 + .../think-orm/src/db/concern/Transaction.php | 117 + .../think-orm/src/db/concern/WhereQuery.php | 540 + .../think-orm/src/db/connector/Mongo.php | 1066 ++ .../think-orm/src/db/connector/Mysql.php | 162 + .../think-orm/src/db/connector/Oracle.php | 117 + .../think-orm/src/db/connector/Pgsql.php | 108 + .../think-orm/src/db/connector/Sqlite.php | 96 + .../think-orm/src/db/connector/Sqlsrv.php | 122 + .../think-orm/src/db/connector/pgsql.sql | 117 + .../src/db/exception/BindParamException.php | 35 + .../db/exception/DataNotFoundException.php | 43 + .../src/db/exception/DbException.php | 81 + .../db/exception/InvalidArgumentException.php | 21 + .../src/db/exception/ModelEventException.php | 19 + .../db/exception/ModelNotFoundException.php | 44 + .../src/db/exception/PDOException.php | 41 + vendor/topthink/think-orm/src/facade/Db.php | 86 + .../think-orm/src/model/Collection.php | 250 + vendor/topthink/think-orm/src/model/Pivot.php | 53 + .../topthink/think-orm/src/model/Relation.php | 258 + .../think-orm/src/model/concern/Attribute.php | 651 + .../src/model/concern/Conversion.php | 285 + .../src/model/concern/ModelEvent.php | 88 + .../think-orm/src/model/concern/OptimLock.php | 85 + .../src/model/concern/RelationShip.php | 781 + .../src/model/concern/SoftDelete.php | 246 + .../think-orm/src/model/concern/TimeStamp.php | 208 + .../src/model/relation/BelongsTo.php | 331 + .../src/model/relation/BelongsToMany.php | 707 + .../think-orm/src/model/relation/HasMany.php | 367 + .../src/model/relation/HasManyThrough.php | 382 + .../think-orm/src/model/relation/HasOne.php | 300 + .../src/model/relation/HasOneThrough.php | 163 + .../src/model/relation/MorphMany.php | 353 + .../think-orm/src/model/relation/MorphOne.php | 280 + .../think-orm/src/model/relation/MorphTo.php | 333 + .../think-orm/src/model/relation/OneToOne.php | 332 + .../src/paginator/driver/Bootstrap.php | 209 + vendor/topthink/think-socketlog/LICENSE | 201 + vendor/topthink/think-socketlog/README.md | 11 + vendor/topthink/think-socketlog/composer.json | 22 + .../think-socketlog/src/SocketLog.php | 280 + vendor/topthink/think-swoole/.gitignore | 6 + vendor/topthink/think-swoole/LICENSE | 201 + vendor/topthink/think-swoole/README.md | 36 + vendor/topthink/think-swoole/composer.json | 42 + vendor/topthink/think-swoole/src/App.php | 32 + .../topthink/think-swoole/src/FileWatcher.php | 51 + vendor/topthink/think-swoole/src/Http.php | 66 + vendor/topthink/think-swoole/src/Manager.php | 282 + .../topthink/think-swoole/src/PidManager.php | 119 + vendor/topthink/think-swoole/src/Sandbox.php | 255 + vendor/topthink/think-swoole/src/Service.php | 87 + vendor/topthink/think-swoole/src/Table.php | 73 + .../topthink/think-swoole/src/Websocket.php | 228 + .../think-swoole/src/command/RpcInterface.php | 79 + .../think-swoole/src/command/Server.php | 154 + .../src/concerns/InteractsWithHttp.php | 122 + .../src/concerns/InteractsWithPool.php | 78 + .../concerns/InteractsWithPoolConnector.php | 42 + .../src/concerns/InteractsWithRpc.php | 122 + .../src/concerns/InteractsWithServer.php | 97 + .../src/concerns/InteractsWithSwooleTable.php | 72 + .../src/concerns/InteractsWithWebsocket.php | 238 + .../think-swoole/src/config/swoole.php | 94 + .../src/contract/ResetterInterface.php | 17 + .../src/contract/rpc/ParserInterface.php | 39 + .../contract/websocket/HandlerInterface.php | 42 + .../contract/websocket/ParserInterface.php | 29 + .../src/contract/websocket/RoomInterface.php | 61 + .../think-swoole/src/coroutine/Context.php | 96 + .../src/exception/RpcClientException.php | 10 + .../src/exception/RpcResponseException.php | 22 + .../think-swoole/src/facade/Server.php | 22 + vendor/topthink/think-swoole/src/helpers.php | 20 + .../src/middleware/ResetVarDumper.php | 32 + .../topthink/think-swoole/src/pool/Cache.php | 47 + vendor/topthink/think-swoole/src/pool/Db.php | 72 + .../think-swoole/src/pool/cache/Store.php | 10 + .../think-swoole/src/pool/db/Connection.php | 233 + .../src/resetters/ClearInstances.php | 23 + .../src/resetters/ResetConfig.php | 18 + .../think-swoole/src/resetters/ResetEvent.php | 32 + .../src/resetters/ResetService.php | 45 + .../topthink/think-swoole/src/rpc/Error.php | 72 + .../think-swoole/src/rpc/JsonParser.php | 124 + .../think-swoole/src/rpc/Protocol.php | 67 + .../think-swoole/src/rpc/client/Client.php | 87 + .../src/rpc/client/Connection.php | 15 + .../think-swoole/src/rpc/client/Pool.php | 57 + .../think-swoole/src/rpc/client/Proxy.php | 102 + .../src/rpc/server/Dispatcher.php | 169 + .../think-swoole/src/websocket/Pusher.php | 202 + .../think-swoole/src/websocket/Room.php | 30 + .../src/websocket/SimpleParser.php | 46 + .../think-swoole/src/websocket/room/Redis.php | 231 + .../think-swoole/src/websocket/room/Table.php | 220 + .../src/websocket/socketio/Controller.php | 54 + .../src/websocket/socketio/Handler.php | 98 + .../src/websocket/socketio/Packet.php | 171 + .../src/websocket/socketio/Parser.php | 45 + vendor/topthink/think-template/.gitignore | 1 + vendor/topthink/think-template/LICENSE | 201 + vendor/topthink/think-template/README.md | 70 + vendor/topthink/think-template/composer.json | 20 + .../topthink/think-template/src/Template.php | 1320 ++ .../think-template/src/facade/Template.php | 83 + .../think-template/src/template/TagLib.php | 349 + .../src/template/driver/File.php | 83 + .../exception/TemplateNotFoundException.php | 33 + .../think-template/src/template/taglib/Cx.php | 715 + vendor/topthink/think-view/.gitignore | 1 + vendor/topthink/think-view/LICENSE | 201 + vendor/topthink/think-view/README.md | 36 + vendor/topthink/think-view/composer.json | 20 + vendor/topthink/think-view/src/Think.php | 263 + view/massage/tcp/game_list copy.html | 105 + view/massage/tcp/game_list.html | 122 + web/.babelrc | 12 + web/.editorconfig | 9 + web/.eslintignore | 4 + web/.eslintrc.js | 29 + web/.gitignore | 14 + web/.postcssrc.js | 10 + web/README.md | 21 + web/build/build.js | 41 + web/build/check-versions.js | 54 + web/build/logo.png | Bin 0 -> 6849 bytes web/build/utils.js | 121 + web/build/vue-loader.conf.js | 22 + web/build/webpack.base.conf.js | 96 + web/build/webpack.dev.conf.js | 95 + web/build/webpack.prod.conf.js | 145 + web/config/dev.env.js | 7 + web/config/index.js | 76 + web/config/prod.env.js | 4 + web/index.html | 12 + web/package-lock.json | 12534 ++++++++++++++++ web/package.json | 81 + web/runtime/services.php | 6 + web/src/App.vue | 21 + web/src/api/index.js | 127 + web/src/api/modules/businessCard.js | 1 + web/src/api/modules/index.js | 5 + web/src/api/modules/survey.js | 7 + web/src/assets/logo.png | Bin 0 -> 6849 bytes web/src/components/ad.vue | 65 + web/src/components/basics/index.js | 12 + web/src/components/basics/lbButton.vue | 71 + web/src/components/basics/lbPage.vue | 96 + web/src/components/basics/lbSwitch.vue | 109 + web/src/components/basics/lbTips.vue | 30 + web/src/components/basics/topNav.vue | 85 + web/src/components/container.vue | 52 + web/src/components/footer.vue | 29 + web/src/components/header.vue | 127 + web/src/components/layout.vue | 67 + web/src/components/sidebar.vue | 181 + web/src/i18n/index.js | 15 + web/src/i18n/langs/en.json | 26 + web/src/i18n/langs/zh.json | 63 + web/src/main.js | 35 + web/src/permission.js | 645 + web/src/router/_import_development.js | 1 + web/src/router/_import_production.js | 1 + web/src/router/index.js | 143 + web/src/router/test.js | 109 + web/src/store/index.js | 22 + web/src/store/modules/operate.js | 18 + web/src/store/modules/routes.js | 65 + web/src/style/icon.css | 132 + web/src/style/reset.css | 65 + web/src/style/theme.scss | 3 + web/src/utils/reg.js | 7 + web/src/view/404/index.vue | 19 + web/src/view/businessCard/manage/index.vue | 41 + .../businessCard/manage/subPage/cardMange.vue | 176 + .../manage/subPage/defaultContent.vue | 374 + web/src/view/businessCard/manage/tag.vue | 95 + web/src/view/businessCard/set/clientSet.vue | 108 + web/src/view/businessCard/set/mobileSet.vue | 94 + web/src/view/dynamic/comment.vue | 160 + web/src/view/dynamic/manage.vue | 200 + web/src/view/login/index.vue | 21 + web/src/view/malls/goods/classify.vue | 70 + web/src/view/malls/goods/list.vue | 164 + web/src/view/malls/order/manage.vue | 164 + web/src/view/malls/order/refund.vue | 163 + web/src/view/malls/set/deal.vue | 46 + web/src/view/malls/set/payment.vue | 73 + web/src/view/malls/set/staffGoods.vue | 65 + web/src/view/malls/set/subpage/overTime.vue | 54 + web/src/view/malls/set/subpage/pickUp.vue | 58 + web/src/view/survey/index.vue | 37 + web/src/view/website/case/case.vue | 19 + web/src/view/website/case/manage.vue | 19 + web/src/view/website/news/list.vue | 19 + web/src/view/website/news/manage.vue | 19 + web/src/view/website/other/about.vue | 19 + web/src/view/website/other/business.vue | 19 + web/src/view/website/other/history.vue | 19 + web/src/view/website/other/recruit.vue | 19 + web/src/view/website/product/list.vue | 19 + web/src/view/website/product/manage.vue | 19 + web/static/.gitkeep | 0 weixinpay/doc/README | 76 + weixinpay/doc/README.doc | Bin 0 -> 29696 bytes weixinpay/example/WxPay.JsApiPay.php | 1 + weixinpay/example/WxPay.MicroPay.php | 147 + weixinpay/example/WxPay.NativePay.php | 56 + weixinpay/example/download.php | 40 + weixinpay/example/jsapi.php | 127 + weixinpay/example/log.php | 125 + weixinpay/example/micropay.php | 56 + weixinpay/example/native.php | 59 + weixinpay/example/native_notify.php | 72 + weixinpay/example/notify.php | 53 + weixinpay/example/orderquery.php | 53 + weixinpay/example/phpqrcode/phpqrcode.php | 3312 ++++ weixinpay/example/qrcode.php | 5 + weixinpay/example/refund.php | 71 + weixinpay/example/refundquery.php | 73 + weixinpay/image/bk.png | Bin 0 -> 1505 bytes weixinpay/image/image001.jpg | Bin 0 -> 27022 bytes weixinpay/image/image002.png | Bin 0 -> 11782 bytes weixinpay/lib/WechatAppPay.php | 374 + weixinpay/lib/WxMchPay.php | 93 + weixinpay/lib/WxOrderNotify.php | 97 + weixinpay/lib/WxPay.Api.php | 591 + weixinpay/lib/WxPay.Config.php | 59 + weixinpay/lib/WxPay.Data.php | 2983 ++++ weixinpay/lib/WxPay.Exception.php | 13 + weixinpay/lib/WxPay.Notify.php | 93 + 1510 files changed, 213008 insertions(+) create mode 100644 LICENSE.txt create mode 100644 Schema/AdminAdminCreateUserRequest.json create mode 100644 Schema/AdminAdminUpdateUserRequest.json create mode 100644 Schema/AdminAuthAuthRequest.json create mode 100644 Schema/AdminConfigSetAppConfigRequest.json create mode 100644 Schema/AdminConfigSetTabbarRequest.json create mode 100644 Schema/CustomerAdminAddQuestionnairRequest.json create mode 100644 Schema/CustomerAdminAddRelyTypeRequest.json create mode 100644 Schema/CustomerAdminAddReplyRequest.json create mode 100644 Schema/CustomerAdminSetQuestionnairConfigRequest.json create mode 100644 Schema/CustomerAdminUpdateQuestionnairRequest.json create mode 100644 Schema/CustomerAdminUpdateRelyTypeRequest.json create mode 100644 Schema/CustomerAdminUpdateReplyRequest.json create mode 100644 Schema/DynamicAdminoffUpdateContentRequest.json create mode 100644 Schema/TestRequest.json create mode 100644 Schema/UserTestRequest.json create mode 100644 Schema/UserUserRequest.json create mode 100644 build.example.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 route/app.php create mode 100644 think create mode 100644 vendor/adbario/php-dot-notation/LICENSE.md create mode 100644 vendor/adbario/php-dot-notation/composer.json create mode 100644 vendor/adbario/php-dot-notation/src/Dot.php create mode 100644 vendor/adbario/php-dot-notation/src/helpers.php create mode 100644 vendor/alibabacloud/client/CHANGELOG.md create mode 100644 vendor/alibabacloud/client/CONTRIBUTING.md create mode 100644 vendor/alibabacloud/client/LICENSE.md create mode 100644 vendor/alibabacloud/client/NOTICE.md create mode 100644 vendor/alibabacloud/client/README-zh-CN.md create mode 100644 vendor/alibabacloud/client/README.md create mode 100644 vendor/alibabacloud/client/UPGRADING.md create mode 100644 vendor/alibabacloud/client/composer.json create mode 100644 vendor/alibabacloud/client/src/Accept.php create mode 100644 vendor/alibabacloud/client/src/AlibabaCloud.php create mode 100644 vendor/alibabacloud/client/src/Clients/AccessKeyClient.php create mode 100644 vendor/alibabacloud/client/src/Clients/BearerTokenClient.php create mode 100644 vendor/alibabacloud/client/src/Clients/Client.php create mode 100644 vendor/alibabacloud/client/src/Clients/EcsRamRoleClient.php create mode 100644 vendor/alibabacloud/client/src/Clients/ManageTrait.php create mode 100644 vendor/alibabacloud/client/src/Clients/RamRoleArnClient.php create mode 100644 vendor/alibabacloud/client/src/Clients/RsaKeyPairClient.php create mode 100644 vendor/alibabacloud/client/src/Clients/StsClient.php create mode 100644 vendor/alibabacloud/client/src/Config/Config.php create mode 100644 vendor/alibabacloud/client/src/Config/Data.php create mode 100644 vendor/alibabacloud/client/src/Credentials/AccessKeyCredential.php create mode 100644 vendor/alibabacloud/client/src/Credentials/BearerTokenCredential.php create mode 100644 vendor/alibabacloud/client/src/Credentials/CredentialsInterface.php create mode 100644 vendor/alibabacloud/client/src/Credentials/EcsRamRoleCredential.php create mode 100644 vendor/alibabacloud/client/src/Credentials/Ini/CreateTrait.php create mode 100644 vendor/alibabacloud/client/src/Credentials/Ini/IniCredential.php create mode 100644 vendor/alibabacloud/client/src/Credentials/Ini/OptionsTrait.php create mode 100644 vendor/alibabacloud/client/src/Credentials/Providers/CredentialsProvider.php create mode 100644 vendor/alibabacloud/client/src/Credentials/Providers/EcsRamRoleProvider.php create mode 100644 vendor/alibabacloud/client/src/Credentials/Providers/Provider.php create mode 100644 vendor/alibabacloud/client/src/Credentials/Providers/RamRoleArnProvider.php create mode 100644 vendor/alibabacloud/client/src/Credentials/Providers/RsaKeyPairProvider.php create mode 100644 vendor/alibabacloud/client/src/Credentials/RamRoleArnCredential.php create mode 100644 vendor/alibabacloud/client/src/Credentials/Requests/AssumeRole.php create mode 100644 vendor/alibabacloud/client/src/Credentials/Requests/GenerateSessionAccessKey.php create mode 100644 vendor/alibabacloud/client/src/Credentials/RsaKeyPairCredential.php create mode 100644 vendor/alibabacloud/client/src/Credentials/StsCredential.php create mode 100644 vendor/alibabacloud/client/src/DefaultAcsClient.php create mode 100644 vendor/alibabacloud/client/src/Encode.php create mode 100644 vendor/alibabacloud/client/src/Exception/AlibabaCloudException.php create mode 100644 vendor/alibabacloud/client/src/Exception/ClientException.php create mode 100644 vendor/alibabacloud/client/src/Exception/ServerException.php create mode 100644 vendor/alibabacloud/client/src/Filter/ApiFilter.php create mode 100644 vendor/alibabacloud/client/src/Filter/ClientFilter.php create mode 100644 vendor/alibabacloud/client/src/Filter/CredentialFilter.php create mode 100644 vendor/alibabacloud/client/src/Filter/Filter.php create mode 100644 vendor/alibabacloud/client/src/Filter/HttpFilter.php create mode 100644 vendor/alibabacloud/client/src/Functions.php create mode 100644 vendor/alibabacloud/client/src/Log/LogFormatter.php create mode 100644 vendor/alibabacloud/client/src/Profile/DefaultProfile.php create mode 100644 vendor/alibabacloud/client/src/Regions/EndpointProvider.php create mode 100644 vendor/alibabacloud/client/src/Regions/LocationService.php create mode 100644 vendor/alibabacloud/client/src/Regions/LocationServiceRequest.php create mode 100644 vendor/alibabacloud/client/src/Release.php create mode 100644 vendor/alibabacloud/client/src/Request/Request.php create mode 100644 vendor/alibabacloud/client/src/Request/RoaRequest.php create mode 100644 vendor/alibabacloud/client/src/Request/RpcRequest.php create mode 100644 vendor/alibabacloud/client/src/Request/Traits/AcsTrait.php create mode 100644 vendor/alibabacloud/client/src/Request/Traits/ClientTrait.php create mode 100644 vendor/alibabacloud/client/src/Request/Traits/DeprecatedRoaTrait.php create mode 100644 vendor/alibabacloud/client/src/Request/Traits/DeprecatedTrait.php create mode 100644 vendor/alibabacloud/client/src/Request/Traits/RetryTrait.php create mode 100644 vendor/alibabacloud/client/src/Request/UserAgent.php create mode 100644 vendor/alibabacloud/client/src/Resolver/ActionResolverTrait.php create mode 100644 vendor/alibabacloud/client/src/Resolver/ApiResolver.php create mode 100644 vendor/alibabacloud/client/src/Resolver/CallTrait.php create mode 100644 vendor/alibabacloud/client/src/Resolver/Roa.php create mode 100644 vendor/alibabacloud/client/src/Resolver/Rpc.php create mode 100644 vendor/alibabacloud/client/src/Resolver/VersionResolver.php create mode 100644 vendor/alibabacloud/client/src/Result/Result.php create mode 100644 vendor/alibabacloud/client/src/SDK.php create mode 100644 vendor/alibabacloud/client/src/Signature/BearerTokenSignature.php create mode 100644 vendor/alibabacloud/client/src/Signature/ShaHmac1Signature.php create mode 100644 vendor/alibabacloud/client/src/Signature/ShaHmac256Signature.php create mode 100644 vendor/alibabacloud/client/src/Signature/ShaHmac256WithRsaSignature.php create mode 100644 vendor/alibabacloud/client/src/Signature/Signature.php create mode 100644 vendor/alibabacloud/client/src/Signature/SignatureInterface.php create mode 100644 vendor/alibabacloud/client/src/Support/Arrays.php create mode 100644 vendor/alibabacloud/client/src/Support/Path.php create mode 100644 vendor/alibabacloud/client/src/Support/Sign.php create mode 100644 vendor/alibabacloud/client/src/Traits/ArrayAccessTrait.php create mode 100644 vendor/alibabacloud/client/src/Traits/ClientTrait.php create mode 100644 vendor/alibabacloud/client/src/Traits/DefaultRegionTrait.php create mode 100644 vendor/alibabacloud/client/src/Traits/EndpointTrait.php create mode 100644 vendor/alibabacloud/client/src/Traits/HasDataTrait.php create mode 100644 vendor/alibabacloud/client/src/Traits/HistoryTrait.php create mode 100644 vendor/alibabacloud/client/src/Traits/HttpTrait.php create mode 100644 vendor/alibabacloud/client/src/Traits/LogTrait.php create mode 100644 vendor/alibabacloud/client/src/Traits/MockTrait.php create mode 100644 vendor/alibabacloud/client/src/Traits/ObjectAccessTrait.php create mode 100644 vendor/alibabacloud/client/src/Traits/RegionTrait.php create mode 100644 vendor/alibabacloud/client/src/Traits/RequestTrait.php create mode 100644 vendor/autoload.php create mode 100644 vendor/bin/jp.php create mode 100644 vendor/bin/validate-json create mode 100644 vendor/clagiordano/weblibs-configmanager/.gitignore create mode 100644 vendor/clagiordano/weblibs-configmanager/.travis.yml create mode 100644 vendor/clagiordano/weblibs-configmanager/README.md create mode 100644 vendor/clagiordano/weblibs-configmanager/composer.json create mode 100644 vendor/clagiordano/weblibs-configmanager/composer.lock create mode 100644 vendor/clagiordano/weblibs-configmanager/phpunit.xml create mode 100644 vendor/clagiordano/weblibs-configmanager/src/ConfigManager.php create mode 100644 vendor/clagiordano/weblibs-configmanager/tests/ConfigManagerTest.php create mode 100644 vendor/clagiordano/weblibs-configmanager/testsdata/sample_config_data.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/danielstjules/stringy/CHANGELOG.md create mode 100644 vendor/danielstjules/stringy/LICENSE.txt create mode 100644 vendor/danielstjules/stringy/README.md create mode 100644 vendor/danielstjules/stringy/composer.json create mode 100644 vendor/danielstjules/stringy/src/Create.php create mode 100644 vendor/danielstjules/stringy/src/StaticStringy.php create mode 100644 vendor/danielstjules/stringy/src/Stringy.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/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/EachPromise.php create mode 100644 vendor/guzzlehttp/promises/src/FulfilledPromise.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/functions.php create mode 100644 vendor/guzzlehttp/promises/src/functions_include.php 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/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/MessageTrait.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/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/functions.php create mode 100644 vendor/guzzlehttp/psr7/src/functions_include.php create mode 100644 vendor/justinrainbow/json-schema/.gitattributes create mode 100644 vendor/justinrainbow/json-schema/LICENSE create mode 100644 vendor/justinrainbow/json-schema/README.md create mode 100644 vendor/justinrainbow/json-schema/bin/validate-json create mode 100644 vendor/justinrainbow/json-schema/composer.json create mode 100644 vendor/justinrainbow/json-schema/phpunit.xml.dist create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/RefResolver.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php create mode 100644 vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php create mode 100644 vendor/league/flysystem-cached-adapter/.editorconfig create mode 100644 vendor/league/flysystem-cached-adapter/.gitignore create mode 100644 vendor/league/flysystem-cached-adapter/.php_cs create mode 100644 vendor/league/flysystem-cached-adapter/.scrutinizer.yml create mode 100644 vendor/league/flysystem-cached-adapter/.travis.yml create mode 100644 vendor/league/flysystem-cached-adapter/LICENSE create mode 100644 vendor/league/flysystem-cached-adapter/clover/.gitignore create mode 100644 vendor/league/flysystem-cached-adapter/composer.json create mode 100644 vendor/league/flysystem-cached-adapter/phpspec.yml create mode 100644 vendor/league/flysystem-cached-adapter/phpunit.php create mode 100644 vendor/league/flysystem-cached-adapter/phpunit.xml create mode 100644 vendor/league/flysystem-cached-adapter/readme.md create mode 100644 vendor/league/flysystem-cached-adapter/spec/CachedAdapterSpec.php create mode 100644 vendor/league/flysystem-cached-adapter/src/CacheInterface.php create mode 100644 vendor/league/flysystem-cached-adapter/src/CachedAdapter.php create mode 100644 vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php create mode 100644 vendor/league/flysystem-cached-adapter/src/Storage/Adapter.php create mode 100644 vendor/league/flysystem-cached-adapter/src/Storage/Memcached.php create mode 100644 vendor/league/flysystem-cached-adapter/src/Storage/Memory.php create mode 100644 vendor/league/flysystem-cached-adapter/src/Storage/Noop.php create mode 100644 vendor/league/flysystem-cached-adapter/src/Storage/PhpRedis.php create mode 100644 vendor/league/flysystem-cached-adapter/src/Storage/Predis.php create mode 100644 vendor/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php create mode 100644 vendor/league/flysystem-cached-adapter/src/Storage/Stash.php create mode 100644 vendor/league/flysystem-cached-adapter/tests/AdapterCacheTests.php create mode 100644 vendor/league/flysystem-cached-adapter/tests/InspectionTests.php create mode 100644 vendor/league/flysystem-cached-adapter/tests/MemcachedTests.php create mode 100644 vendor/league/flysystem-cached-adapter/tests/MemoryCacheTests.php create mode 100644 vendor/league/flysystem-cached-adapter/tests/NoopCacheTests.php create mode 100644 vendor/league/flysystem-cached-adapter/tests/PhpRedisTests.php create mode 100644 vendor/league/flysystem-cached-adapter/tests/PredisTests.php create mode 100644 vendor/league/flysystem-cached-adapter/tests/Psr6CacheTest.php create mode 100644 vendor/league/flysystem-cached-adapter/tests/StashTest.php create mode 100644 vendor/league/flysystem/LICENSE create mode 100644 vendor/league/flysystem/composer.json create mode 100644 vendor/league/flysystem/deprecations.md create mode 100644 vendor/league/flysystem/src/Adapter/AbstractAdapter.php create mode 100644 vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php create mode 100644 vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php create mode 100644 vendor/league/flysystem/src/Adapter/Ftp.php create mode 100644 vendor/league/flysystem/src/Adapter/Ftpd.php create mode 100644 vendor/league/flysystem/src/Adapter/Local.php create mode 100644 vendor/league/flysystem/src/Adapter/NullAdapter.php create mode 100644 vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php create mode 100644 vendor/league/flysystem/src/Adapter/Polyfill/StreamedCopyTrait.php create mode 100644 vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php create mode 100644 vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php create mode 100644 vendor/league/flysystem/src/Adapter/Polyfill/StreamedWritingTrait.php create mode 100644 vendor/league/flysystem/src/Adapter/SynologyFtp.php create mode 100644 vendor/league/flysystem/src/AdapterInterface.php create mode 100644 vendor/league/flysystem/src/Config.php create mode 100644 vendor/league/flysystem/src/ConfigAwareTrait.php create mode 100644 vendor/league/flysystem/src/Directory.php create mode 100644 vendor/league/flysystem/src/Exception.php create mode 100644 vendor/league/flysystem/src/File.php create mode 100644 vendor/league/flysystem/src/FileExistsException.php create mode 100644 vendor/league/flysystem/src/FileNotFoundException.php create mode 100644 vendor/league/flysystem/src/Filesystem.php create mode 100644 vendor/league/flysystem/src/FilesystemInterface.php create mode 100644 vendor/league/flysystem/src/FilesystemNotFoundException.php create mode 100644 vendor/league/flysystem/src/Handler.php create mode 100644 vendor/league/flysystem/src/MountManager.php create mode 100644 vendor/league/flysystem/src/NotSupportedException.php create mode 100644 vendor/league/flysystem/src/Plugin/AbstractPlugin.php create mode 100644 vendor/league/flysystem/src/Plugin/EmptyDir.php create mode 100644 vendor/league/flysystem/src/Plugin/ForcedCopy.php create mode 100644 vendor/league/flysystem/src/Plugin/ForcedRename.php create mode 100644 vendor/league/flysystem/src/Plugin/GetWithMetadata.php create mode 100644 vendor/league/flysystem/src/Plugin/ListFiles.php create mode 100644 vendor/league/flysystem/src/Plugin/ListPaths.php create mode 100644 vendor/league/flysystem/src/Plugin/ListWith.php create mode 100644 vendor/league/flysystem/src/Plugin/PluggableTrait.php create mode 100644 vendor/league/flysystem/src/Plugin/PluginNotFoundException.php create mode 100644 vendor/league/flysystem/src/PluginInterface.php create mode 100644 vendor/league/flysystem/src/ReadInterface.php create mode 100644 vendor/league/flysystem/src/RootViolationException.php create mode 100644 vendor/league/flysystem/src/SafeStorage.php create mode 100644 vendor/league/flysystem/src/UnreadableFileException.php create mode 100644 vendor/league/flysystem/src/Util.php create mode 100644 vendor/league/flysystem/src/Util/ContentListingFormatter.php create mode 100644 vendor/league/flysystem/src/Util/MimeType.php create mode 100644 vendor/league/flysystem/src/Util/StreamHasher.php create mode 100644 vendor/mtdowling/jmespath.php/.gitignore create mode 100644 vendor/mtdowling/jmespath.php/.travis.yml create mode 100644 vendor/mtdowling/jmespath.php/CHANGELOG.md create mode 100644 vendor/mtdowling/jmespath.php/LICENSE create mode 100644 vendor/mtdowling/jmespath.php/Makefile create mode 100644 vendor/mtdowling/jmespath.php/README.rst create mode 100644 vendor/mtdowling/jmespath.php/bin/jp.php create mode 100644 vendor/mtdowling/jmespath.php/bin/perf.php create mode 100644 vendor/mtdowling/jmespath.php/composer.json create mode 100644 vendor/mtdowling/jmespath.php/phpunit.xml.dist create mode 100644 vendor/mtdowling/jmespath.php/src/AstRuntime.php create mode 100644 vendor/mtdowling/jmespath.php/src/CompilerRuntime.php create mode 100644 vendor/mtdowling/jmespath.php/src/DebugRuntime.php create mode 100644 vendor/mtdowling/jmespath.php/src/Env.php create mode 100644 vendor/mtdowling/jmespath.php/src/FnDispatcher.php create mode 100644 vendor/mtdowling/jmespath.php/src/JmesPath.php create mode 100644 vendor/mtdowling/jmespath.php/src/Lexer.php create mode 100644 vendor/mtdowling/jmespath.php/src/Parser.php create mode 100644 vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php create mode 100644 vendor/mtdowling/jmespath.php/src/TreeCompiler.php create mode 100644 vendor/mtdowling/jmespath.php/src/TreeInterpreter.php create mode 100644 vendor/mtdowling/jmespath.php/src/Utils.php create mode 100644 vendor/mtdowling/jmespath.php/tests/ComplianceTest.php create mode 100644 vendor/mtdowling/jmespath.php/tests/EnvTest.php create mode 100644 vendor/mtdowling/jmespath.php/tests/FnDispatcherTest.php create mode 100644 vendor/mtdowling/jmespath.php/tests/LexerTest.php create mode 100644 vendor/mtdowling/jmespath.php/tests/ParserTest.php create mode 100644 vendor/mtdowling/jmespath.php/tests/SyntaxErrorExceptionTest.php create mode 100644 vendor/mtdowling/jmespath.php/tests/TreeCompilerTest.php create mode 100644 vendor/mtdowling/jmespath.php/tests/TreeInterpreterTest.php create mode 100644 vendor/mtdowling/jmespath.php/tests/UtilsTest.php create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/basic.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/boolean.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/current.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/escape.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/filters.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/functions.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/identifiers.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/indices.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/literal.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/multiselect.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/perf/basic.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/perf/deep_hierarchy.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/perf/deep_projection.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/perf/functions.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/perf/multiwildcard.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/perf/wildcardindex.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/pipe.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/slice.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/syntax.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/unicode.json create mode 100644 vendor/mtdowling/jmespath.php/tests/compliance/wildcard.json create mode 100644 vendor/nette/php-generator/composer.json create mode 100644 vendor/nette/php-generator/contributing.md create mode 100644 vendor/nette/php-generator/license.md create mode 100644 vendor/nette/php-generator/readme.md create mode 100644 vendor/nette/php-generator/src/PhpGenerator/ClassType.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Closure.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Constant.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Dumper.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Factory.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/GlobalFunction.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Helpers.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Method.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Parameter.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/PhpFile.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/PhpLiteral.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/PhpNamespace.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Printer.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Property.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/PsrPrinter.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Traits/CommentAware.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Traits/NameAware.php create mode 100644 vendor/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php create mode 100644 vendor/nette/utils/.phpstorm.meta.php create mode 100644 vendor/nette/utils/composer.json create mode 100644 vendor/nette/utils/contributing.md create mode 100644 vendor/nette/utils/license.md create mode 100644 vendor/nette/utils/readme.md create mode 100644 vendor/nette/utils/src/Iterators/CachingIterator.php create mode 100644 vendor/nette/utils/src/Iterators/Mapper.php create mode 100644 vendor/nette/utils/src/Utils/ArrayHash.php create mode 100644 vendor/nette/utils/src/Utils/ArrayList.php create mode 100644 vendor/nette/utils/src/Utils/Arrays.php create mode 100644 vendor/nette/utils/src/Utils/Callback.php create mode 100644 vendor/nette/utils/src/Utils/DateTime.php create mode 100644 vendor/nette/utils/src/Utils/FileSystem.php create mode 100644 vendor/nette/utils/src/Utils/Html.php create mode 100644 vendor/nette/utils/src/Utils/IHtmlString.php create mode 100644 vendor/nette/utils/src/Utils/ITranslator.php create mode 100644 vendor/nette/utils/src/Utils/Image.php create mode 100644 vendor/nette/utils/src/Utils/Json.php create mode 100644 vendor/nette/utils/src/Utils/ObjectHelpers.php create mode 100644 vendor/nette/utils/src/Utils/ObjectMixin.php create mode 100644 vendor/nette/utils/src/Utils/Paginator.php create mode 100644 vendor/nette/utils/src/Utils/Random.php create mode 100644 vendor/nette/utils/src/Utils/Reflection.php create mode 100644 vendor/nette/utils/src/Utils/SmartObject.php create mode 100644 vendor/nette/utils/src/Utils/StaticClass.php create mode 100644 vendor/nette/utils/src/Utils/Strings.php create mode 100644 vendor/nette/utils/src/Utils/Validators.php create mode 100644 vendor/nette/utils/src/Utils/exceptions.php create mode 100644 vendor/opis/closure/CHANGELOG.md create mode 100644 vendor/opis/closure/LICENSE create mode 100644 vendor/opis/closure/NOTICE create mode 100644 vendor/opis/closure/README.md create mode 100644 vendor/opis/closure/autoload.php create mode 100644 vendor/opis/closure/composer.json create mode 100644 vendor/opis/closure/functions.php create mode 100644 vendor/opis/closure/src/Analyzer.php create mode 100644 vendor/opis/closure/src/ClosureContext.php create mode 100644 vendor/opis/closure/src/ClosureScope.php create mode 100644 vendor/opis/closure/src/ClosureStream.php create mode 100644 vendor/opis/closure/src/ISecurityProvider.php create mode 100644 vendor/opis/closure/src/ReflectionClosure.php create mode 100644 vendor/opis/closure/src/SecurityException.php create mode 100644 vendor/opis/closure/src/SecurityProvider.php create mode 100644 vendor/opis/closure/src/SelfReference.php create mode 100644 vendor/opis/closure/src/SerializableClosure.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/predis/predis/CHANGELOG.md create mode 100644 vendor/predis/predis/CONTRIBUTING.md create mode 100644 vendor/predis/predis/FAQ.md create mode 100644 vendor/predis/predis/LICENSE create mode 100644 vendor/predis/predis/README.md create mode 100644 vendor/predis/predis/VERSION create mode 100644 vendor/predis/predis/autoload.php create mode 100644 vendor/predis/predis/bin/create-command-test create mode 100644 vendor/predis/predis/bin/create-pear create mode 100644 vendor/predis/predis/bin/create-phar create mode 100644 vendor/predis/predis/bin/create-single-file create mode 100644 vendor/predis/predis/composer.json create mode 100644 vendor/predis/predis/examples/custom_cluster_distributor.php create mode 100644 vendor/predis/predis/examples/debuggable_connection.php create mode 100644 vendor/predis/predis/examples/dispatcher_loop.php create mode 100644 vendor/predis/predis/examples/executing_redis_commands.php create mode 100644 vendor/predis/predis/examples/key_prefixing.php create mode 100644 vendor/predis/predis/examples/lua_scripting_abstraction.php create mode 100644 vendor/predis/predis/examples/monitor_consumer.php create mode 100644 vendor/predis/predis/examples/pipelining_commands.php create mode 100644 vendor/predis/predis/examples/pubsub_consumer.php create mode 100644 vendor/predis/predis/examples/redis_collections_iterators.php create mode 100644 vendor/predis/predis/examples/replication_complex.php create mode 100644 vendor/predis/predis/examples/replication_sentinel.php create mode 100644 vendor/predis/predis/examples/replication_simple.php create mode 100644 vendor/predis/predis/examples/session_handler.php create mode 100644 vendor/predis/predis/examples/shared.php create mode 100644 vendor/predis/predis/examples/transaction_using_cas.php create mode 100644 vendor/predis/predis/package.ini create mode 100644 vendor/predis/predis/src/Autoloader.php create mode 100644 vendor/predis/predis/src/Client.php create mode 100644 vendor/predis/predis/src/ClientContextInterface.php create mode 100644 vendor/predis/predis/src/ClientException.php create mode 100644 vendor/predis/predis/src/ClientInterface.php create mode 100644 vendor/predis/predis/src/Cluster/ClusterStrategy.php create mode 100644 vendor/predis/predis/src/Cluster/Distributor/DistributorInterface.php create mode 100644 vendor/predis/predis/src/Cluster/Distributor/EmptyRingException.php create mode 100644 vendor/predis/predis/src/Cluster/Distributor/HashRing.php create mode 100644 vendor/predis/predis/src/Cluster/Distributor/KetamaRing.php create mode 100644 vendor/predis/predis/src/Cluster/Hash/CRC16.php create mode 100644 vendor/predis/predis/src/Cluster/Hash/HashGeneratorInterface.php create mode 100644 vendor/predis/predis/src/Cluster/PredisStrategy.php create mode 100644 vendor/predis/predis/src/Cluster/RedisStrategy.php create mode 100644 vendor/predis/predis/src/Cluster/StrategyInterface.php create mode 100644 vendor/predis/predis/src/Collection/Iterator/CursorBasedIterator.php create mode 100644 vendor/predis/predis/src/Collection/Iterator/HashKey.php create mode 100644 vendor/predis/predis/src/Collection/Iterator/Keyspace.php create mode 100644 vendor/predis/predis/src/Collection/Iterator/ListKey.php create mode 100644 vendor/predis/predis/src/Collection/Iterator/SetKey.php create mode 100644 vendor/predis/predis/src/Collection/Iterator/SortedSetKey.php create mode 100644 vendor/predis/predis/src/Command/Command.php create mode 100644 vendor/predis/predis/src/Command/CommandInterface.php create mode 100644 vendor/predis/predis/src/Command/ConnectionAuth.php create mode 100644 vendor/predis/predis/src/Command/ConnectionEcho.php create mode 100644 vendor/predis/predis/src/Command/ConnectionPing.php create mode 100644 vendor/predis/predis/src/Command/ConnectionQuit.php create mode 100644 vendor/predis/predis/src/Command/ConnectionSelect.php create mode 100644 vendor/predis/predis/src/Command/GeospatialGeoAdd.php create mode 100644 vendor/predis/predis/src/Command/GeospatialGeoDist.php create mode 100644 vendor/predis/predis/src/Command/GeospatialGeoHash.php create mode 100644 vendor/predis/predis/src/Command/GeospatialGeoPos.php create mode 100644 vendor/predis/predis/src/Command/GeospatialGeoRadius.php create mode 100644 vendor/predis/predis/src/Command/GeospatialGeoRadiusByMember.php create mode 100644 vendor/predis/predis/src/Command/HashDelete.php create mode 100644 vendor/predis/predis/src/Command/HashExists.php create mode 100644 vendor/predis/predis/src/Command/HashGet.php create mode 100644 vendor/predis/predis/src/Command/HashGetAll.php create mode 100644 vendor/predis/predis/src/Command/HashGetMultiple.php create mode 100644 vendor/predis/predis/src/Command/HashIncrementBy.php create mode 100644 vendor/predis/predis/src/Command/HashIncrementByFloat.php create mode 100644 vendor/predis/predis/src/Command/HashKeys.php create mode 100644 vendor/predis/predis/src/Command/HashLength.php create mode 100644 vendor/predis/predis/src/Command/HashScan.php create mode 100644 vendor/predis/predis/src/Command/HashSet.php create mode 100644 vendor/predis/predis/src/Command/HashSetMultiple.php create mode 100644 vendor/predis/predis/src/Command/HashSetPreserve.php create mode 100644 vendor/predis/predis/src/Command/HashStringLength.php create mode 100644 vendor/predis/predis/src/Command/HashValues.php create mode 100644 vendor/predis/predis/src/Command/HyperLogLogAdd.php create mode 100644 vendor/predis/predis/src/Command/HyperLogLogCount.php create mode 100644 vendor/predis/predis/src/Command/HyperLogLogMerge.php create mode 100644 vendor/predis/predis/src/Command/KeyDelete.php create mode 100644 vendor/predis/predis/src/Command/KeyDump.php create mode 100644 vendor/predis/predis/src/Command/KeyExists.php create mode 100644 vendor/predis/predis/src/Command/KeyExpire.php create mode 100644 vendor/predis/predis/src/Command/KeyExpireAt.php create mode 100644 vendor/predis/predis/src/Command/KeyKeys.php create mode 100644 vendor/predis/predis/src/Command/KeyMigrate.php create mode 100644 vendor/predis/predis/src/Command/KeyMove.php create mode 100644 vendor/predis/predis/src/Command/KeyPersist.php create mode 100644 vendor/predis/predis/src/Command/KeyPreciseExpire.php create mode 100644 vendor/predis/predis/src/Command/KeyPreciseExpireAt.php create mode 100644 vendor/predis/predis/src/Command/KeyPreciseTimeToLive.php create mode 100644 vendor/predis/predis/src/Command/KeyRandom.php create mode 100644 vendor/predis/predis/src/Command/KeyRename.php create mode 100644 vendor/predis/predis/src/Command/KeyRenamePreserve.php create mode 100644 vendor/predis/predis/src/Command/KeyRestore.php create mode 100644 vendor/predis/predis/src/Command/KeyScan.php create mode 100644 vendor/predis/predis/src/Command/KeySort.php create mode 100644 vendor/predis/predis/src/Command/KeyTimeToLive.php create mode 100644 vendor/predis/predis/src/Command/KeyType.php create mode 100644 vendor/predis/predis/src/Command/ListIndex.php create mode 100644 vendor/predis/predis/src/Command/ListInsert.php create mode 100644 vendor/predis/predis/src/Command/ListLength.php create mode 100644 vendor/predis/predis/src/Command/ListPopFirst.php create mode 100644 vendor/predis/predis/src/Command/ListPopFirstBlocking.php create mode 100644 vendor/predis/predis/src/Command/ListPopLast.php create mode 100644 vendor/predis/predis/src/Command/ListPopLastBlocking.php create mode 100644 vendor/predis/predis/src/Command/ListPopLastPushHead.php create mode 100644 vendor/predis/predis/src/Command/ListPopLastPushHeadBlocking.php create mode 100644 vendor/predis/predis/src/Command/ListPushHead.php create mode 100644 vendor/predis/predis/src/Command/ListPushHeadX.php create mode 100644 vendor/predis/predis/src/Command/ListPushTail.php create mode 100644 vendor/predis/predis/src/Command/ListPushTailX.php create mode 100644 vendor/predis/predis/src/Command/ListRange.php create mode 100644 vendor/predis/predis/src/Command/ListRemove.php create mode 100644 vendor/predis/predis/src/Command/ListSet.php create mode 100644 vendor/predis/predis/src/Command/ListTrim.php create mode 100644 vendor/predis/predis/src/Command/PrefixableCommandInterface.php create mode 100644 vendor/predis/predis/src/Command/Processor/KeyPrefixProcessor.php create mode 100644 vendor/predis/predis/src/Command/Processor/ProcessorChain.php create mode 100644 vendor/predis/predis/src/Command/Processor/ProcessorInterface.php create mode 100644 vendor/predis/predis/src/Command/PubSubPublish.php create mode 100644 vendor/predis/predis/src/Command/PubSubPubsub.php create mode 100644 vendor/predis/predis/src/Command/PubSubSubscribe.php create mode 100644 vendor/predis/predis/src/Command/PubSubSubscribeByPattern.php create mode 100644 vendor/predis/predis/src/Command/PubSubUnsubscribe.php create mode 100644 vendor/predis/predis/src/Command/PubSubUnsubscribeByPattern.php create mode 100644 vendor/predis/predis/src/Command/RawCommand.php create mode 100644 vendor/predis/predis/src/Command/ScriptCommand.php create mode 100644 vendor/predis/predis/src/Command/ServerBackgroundRewriteAOF.php create mode 100644 vendor/predis/predis/src/Command/ServerBackgroundSave.php create mode 100644 vendor/predis/predis/src/Command/ServerClient.php create mode 100644 vendor/predis/predis/src/Command/ServerCommand.php create mode 100644 vendor/predis/predis/src/Command/ServerConfig.php create mode 100644 vendor/predis/predis/src/Command/ServerDatabaseSize.php create mode 100644 vendor/predis/predis/src/Command/ServerEval.php create mode 100644 vendor/predis/predis/src/Command/ServerEvalSHA.php create mode 100644 vendor/predis/predis/src/Command/ServerFlushAll.php create mode 100644 vendor/predis/predis/src/Command/ServerFlushDatabase.php create mode 100644 vendor/predis/predis/src/Command/ServerInfo.php create mode 100644 vendor/predis/predis/src/Command/ServerInfoV26x.php create mode 100644 vendor/predis/predis/src/Command/ServerLastSave.php create mode 100644 vendor/predis/predis/src/Command/ServerMonitor.php create mode 100644 vendor/predis/predis/src/Command/ServerObject.php create mode 100644 vendor/predis/predis/src/Command/ServerSave.php create mode 100644 vendor/predis/predis/src/Command/ServerScript.php create mode 100644 vendor/predis/predis/src/Command/ServerSentinel.php create mode 100644 vendor/predis/predis/src/Command/ServerShutdown.php create mode 100644 vendor/predis/predis/src/Command/ServerSlaveOf.php create mode 100644 vendor/predis/predis/src/Command/ServerSlowlog.php create mode 100644 vendor/predis/predis/src/Command/ServerTime.php create mode 100644 vendor/predis/predis/src/Command/SetAdd.php create mode 100644 vendor/predis/predis/src/Command/SetCardinality.php create mode 100644 vendor/predis/predis/src/Command/SetDifference.php create mode 100644 vendor/predis/predis/src/Command/SetDifferenceStore.php create mode 100644 vendor/predis/predis/src/Command/SetIntersection.php create mode 100644 vendor/predis/predis/src/Command/SetIntersectionStore.php create mode 100644 vendor/predis/predis/src/Command/SetIsMember.php create mode 100644 vendor/predis/predis/src/Command/SetMembers.php create mode 100644 vendor/predis/predis/src/Command/SetMove.php create mode 100644 vendor/predis/predis/src/Command/SetPop.php create mode 100644 vendor/predis/predis/src/Command/SetRandomMember.php create mode 100644 vendor/predis/predis/src/Command/SetRemove.php create mode 100644 vendor/predis/predis/src/Command/SetScan.php create mode 100644 vendor/predis/predis/src/Command/SetUnion.php create mode 100644 vendor/predis/predis/src/Command/SetUnionStore.php create mode 100644 vendor/predis/predis/src/Command/StringAppend.php create mode 100644 vendor/predis/predis/src/Command/StringBitCount.php create mode 100644 vendor/predis/predis/src/Command/StringBitField.php create mode 100644 vendor/predis/predis/src/Command/StringBitOp.php create mode 100644 vendor/predis/predis/src/Command/StringBitPos.php create mode 100644 vendor/predis/predis/src/Command/StringDecrement.php create mode 100644 vendor/predis/predis/src/Command/StringDecrementBy.php create mode 100644 vendor/predis/predis/src/Command/StringGet.php create mode 100644 vendor/predis/predis/src/Command/StringGetBit.php create mode 100644 vendor/predis/predis/src/Command/StringGetMultiple.php create mode 100644 vendor/predis/predis/src/Command/StringGetRange.php create mode 100644 vendor/predis/predis/src/Command/StringGetSet.php create mode 100644 vendor/predis/predis/src/Command/StringIncrement.php create mode 100644 vendor/predis/predis/src/Command/StringIncrementBy.php create mode 100644 vendor/predis/predis/src/Command/StringIncrementByFloat.php create mode 100644 vendor/predis/predis/src/Command/StringPreciseSetExpire.php create mode 100644 vendor/predis/predis/src/Command/StringSet.php create mode 100644 vendor/predis/predis/src/Command/StringSetBit.php create mode 100644 vendor/predis/predis/src/Command/StringSetExpire.php create mode 100644 vendor/predis/predis/src/Command/StringSetMultiple.php create mode 100644 vendor/predis/predis/src/Command/StringSetMultiplePreserve.php create mode 100644 vendor/predis/predis/src/Command/StringSetPreserve.php create mode 100644 vendor/predis/predis/src/Command/StringSetRange.php create mode 100644 vendor/predis/predis/src/Command/StringStrlen.php create mode 100644 vendor/predis/predis/src/Command/StringSubstr.php create mode 100644 vendor/predis/predis/src/Command/TransactionDiscard.php create mode 100644 vendor/predis/predis/src/Command/TransactionExec.php create mode 100644 vendor/predis/predis/src/Command/TransactionMulti.php create mode 100644 vendor/predis/predis/src/Command/TransactionUnwatch.php create mode 100644 vendor/predis/predis/src/Command/TransactionWatch.php create mode 100644 vendor/predis/predis/src/Command/ZSetAdd.php create mode 100644 vendor/predis/predis/src/Command/ZSetCardinality.php create mode 100644 vendor/predis/predis/src/Command/ZSetCount.php create mode 100644 vendor/predis/predis/src/Command/ZSetIncrementBy.php create mode 100644 vendor/predis/predis/src/Command/ZSetIntersectionStore.php create mode 100644 vendor/predis/predis/src/Command/ZSetLexCount.php create mode 100644 vendor/predis/predis/src/Command/ZSetRange.php create mode 100644 vendor/predis/predis/src/Command/ZSetRangeByLex.php create mode 100644 vendor/predis/predis/src/Command/ZSetRangeByScore.php create mode 100644 vendor/predis/predis/src/Command/ZSetRank.php create mode 100644 vendor/predis/predis/src/Command/ZSetRemove.php create mode 100644 vendor/predis/predis/src/Command/ZSetRemoveRangeByLex.php create mode 100644 vendor/predis/predis/src/Command/ZSetRemoveRangeByRank.php create mode 100644 vendor/predis/predis/src/Command/ZSetRemoveRangeByScore.php create mode 100644 vendor/predis/predis/src/Command/ZSetReverseRange.php create mode 100644 vendor/predis/predis/src/Command/ZSetReverseRangeByLex.php create mode 100644 vendor/predis/predis/src/Command/ZSetReverseRangeByScore.php create mode 100644 vendor/predis/predis/src/Command/ZSetReverseRank.php create mode 100644 vendor/predis/predis/src/Command/ZSetScan.php create mode 100644 vendor/predis/predis/src/Command/ZSetScore.php create mode 100644 vendor/predis/predis/src/Command/ZSetUnionStore.php create mode 100644 vendor/predis/predis/src/CommunicationException.php create mode 100644 vendor/predis/predis/src/Configuration/ClusterOption.php create mode 100644 vendor/predis/predis/src/Configuration/ConnectionFactoryOption.php create mode 100644 vendor/predis/predis/src/Configuration/ExceptionsOption.php create mode 100644 vendor/predis/predis/src/Configuration/OptionInterface.php create mode 100644 vendor/predis/predis/src/Configuration/Options.php create mode 100644 vendor/predis/predis/src/Configuration/OptionsInterface.php create mode 100644 vendor/predis/predis/src/Configuration/PrefixOption.php create mode 100644 vendor/predis/predis/src/Configuration/ProfileOption.php create mode 100644 vendor/predis/predis/src/Configuration/ReplicationOption.php create mode 100644 vendor/predis/predis/src/Connection/AbstractConnection.php create mode 100644 vendor/predis/predis/src/Connection/Aggregate/ClusterInterface.php create mode 100644 vendor/predis/predis/src/Connection/Aggregate/MasterSlaveReplication.php create mode 100644 vendor/predis/predis/src/Connection/Aggregate/PredisCluster.php create mode 100644 vendor/predis/predis/src/Connection/Aggregate/RedisCluster.php create mode 100644 vendor/predis/predis/src/Connection/Aggregate/ReplicationInterface.php create mode 100644 vendor/predis/predis/src/Connection/Aggregate/SentinelReplication.php create mode 100644 vendor/predis/predis/src/Connection/AggregateConnectionInterface.php create mode 100644 vendor/predis/predis/src/Connection/CompositeConnectionInterface.php create mode 100644 vendor/predis/predis/src/Connection/CompositeStreamConnection.php create mode 100644 vendor/predis/predis/src/Connection/ConnectionException.php create mode 100644 vendor/predis/predis/src/Connection/ConnectionInterface.php create mode 100644 vendor/predis/predis/src/Connection/Factory.php create mode 100644 vendor/predis/predis/src/Connection/FactoryInterface.php create mode 100644 vendor/predis/predis/src/Connection/NodeConnectionInterface.php create mode 100644 vendor/predis/predis/src/Connection/Parameters.php create mode 100644 vendor/predis/predis/src/Connection/ParametersInterface.php create mode 100644 vendor/predis/predis/src/Connection/PhpiredisSocketConnection.php create mode 100644 vendor/predis/predis/src/Connection/PhpiredisStreamConnection.php create mode 100644 vendor/predis/predis/src/Connection/StreamConnection.php create mode 100644 vendor/predis/predis/src/Connection/WebdisConnection.php create mode 100644 vendor/predis/predis/src/Monitor/Consumer.php create mode 100644 vendor/predis/predis/src/NotSupportedException.php create mode 100644 vendor/predis/predis/src/Pipeline/Atomic.php create mode 100644 vendor/predis/predis/src/Pipeline/ConnectionErrorProof.php create mode 100644 vendor/predis/predis/src/Pipeline/FireAndForget.php create mode 100644 vendor/predis/predis/src/Pipeline/Pipeline.php create mode 100644 vendor/predis/predis/src/PredisException.php create mode 100644 vendor/predis/predis/src/Profile/Factory.php create mode 100644 vendor/predis/predis/src/Profile/ProfileInterface.php create mode 100644 vendor/predis/predis/src/Profile/RedisProfile.php create mode 100644 vendor/predis/predis/src/Profile/RedisUnstable.php create mode 100644 vendor/predis/predis/src/Profile/RedisVersion200.php create mode 100644 vendor/predis/predis/src/Profile/RedisVersion220.php create mode 100644 vendor/predis/predis/src/Profile/RedisVersion240.php create mode 100644 vendor/predis/predis/src/Profile/RedisVersion260.php create mode 100644 vendor/predis/predis/src/Profile/RedisVersion280.php create mode 100644 vendor/predis/predis/src/Profile/RedisVersion300.php create mode 100644 vendor/predis/predis/src/Profile/RedisVersion320.php create mode 100644 vendor/predis/predis/src/Protocol/ProtocolException.php create mode 100644 vendor/predis/predis/src/Protocol/ProtocolProcessorInterface.php create mode 100644 vendor/predis/predis/src/Protocol/RequestSerializerInterface.php create mode 100644 vendor/predis/predis/src/Protocol/ResponseReaderInterface.php create mode 100644 vendor/predis/predis/src/Protocol/Text/CompositeProtocolProcessor.php create mode 100644 vendor/predis/predis/src/Protocol/Text/Handler/BulkResponse.php create mode 100644 vendor/predis/predis/src/Protocol/Text/Handler/ErrorResponse.php create mode 100644 vendor/predis/predis/src/Protocol/Text/Handler/IntegerResponse.php create mode 100644 vendor/predis/predis/src/Protocol/Text/Handler/MultiBulkResponse.php create mode 100644 vendor/predis/predis/src/Protocol/Text/Handler/ResponseHandlerInterface.php create mode 100644 vendor/predis/predis/src/Protocol/Text/Handler/StatusResponse.php create mode 100644 vendor/predis/predis/src/Protocol/Text/Handler/StreamableMultiBulkResponse.php create mode 100644 vendor/predis/predis/src/Protocol/Text/ProtocolProcessor.php create mode 100644 vendor/predis/predis/src/Protocol/Text/RequestSerializer.php create mode 100644 vendor/predis/predis/src/Protocol/Text/ResponseReader.php create mode 100644 vendor/predis/predis/src/PubSub/AbstractConsumer.php create mode 100644 vendor/predis/predis/src/PubSub/Consumer.php create mode 100644 vendor/predis/predis/src/PubSub/DispatcherLoop.php create mode 100644 vendor/predis/predis/src/Replication/MissingMasterException.php create mode 100644 vendor/predis/predis/src/Replication/ReplicationStrategy.php create mode 100644 vendor/predis/predis/src/Replication/RoleException.php create mode 100644 vendor/predis/predis/src/Response/Error.php create mode 100644 vendor/predis/predis/src/Response/ErrorInterface.php create mode 100644 vendor/predis/predis/src/Response/Iterator/MultiBulk.php create mode 100644 vendor/predis/predis/src/Response/Iterator/MultiBulkIterator.php create mode 100644 vendor/predis/predis/src/Response/Iterator/MultiBulkTuple.php create mode 100644 vendor/predis/predis/src/Response/ResponseInterface.php create mode 100644 vendor/predis/predis/src/Response/ServerException.php create mode 100644 vendor/predis/predis/src/Response/Status.php create mode 100644 vendor/predis/predis/src/Session/Handler.php create mode 100644 vendor/predis/predis/src/Transaction/AbortedMultiExecException.php create mode 100644 vendor/predis/predis/src/Transaction/MultiExec.php create mode 100644 vendor/predis/predis/src/Transaction/MultiExecState.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/.gitignore 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/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/ramsey/uuid/CHANGELOG.md create mode 100644 vendor/ramsey/uuid/LICENSE create mode 100644 vendor/ramsey/uuid/README.md create mode 100644 vendor/ramsey/uuid/composer.json create mode 100644 vendor/ramsey/uuid/src/BinaryUtils.php create mode 100644 vendor/ramsey/uuid/src/Builder/DefaultUuidBuilder.php create mode 100644 vendor/ramsey/uuid/src/Builder/DegradedUuidBuilder.php create mode 100644 vendor/ramsey/uuid/src/Builder/UuidBuilderInterface.php create mode 100644 vendor/ramsey/uuid/src/Codec/CodecInterface.php create mode 100644 vendor/ramsey/uuid/src/Codec/GuidStringCodec.php create mode 100644 vendor/ramsey/uuid/src/Codec/OrderedTimeCodec.php create mode 100644 vendor/ramsey/uuid/src/Codec/StringCodec.php create mode 100644 vendor/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php create mode 100644 vendor/ramsey/uuid/src/Codec/TimestampLastCombCodec.php create mode 100644 vendor/ramsey/uuid/src/Converter/Number/BigNumberConverter.php create mode 100644 vendor/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php create mode 100644 vendor/ramsey/uuid/src/Converter/NumberConverterInterface.php create mode 100644 vendor/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php create mode 100644 vendor/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php create mode 100644 vendor/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php create mode 100644 vendor/ramsey/uuid/src/Converter/TimeConverterInterface.php create mode 100644 vendor/ramsey/uuid/src/DegradedUuid.php create mode 100644 vendor/ramsey/uuid/src/Exception/InvalidUuidStringException.php create mode 100644 vendor/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php create mode 100644 vendor/ramsey/uuid/src/Exception/UnsupportedOperationException.php create mode 100644 vendor/ramsey/uuid/src/FeatureSet.php create mode 100644 vendor/ramsey/uuid/src/Generator/CombGenerator.php create mode 100644 vendor/ramsey/uuid/src/Generator/DefaultTimeGenerator.php create mode 100644 vendor/ramsey/uuid/src/Generator/MtRandGenerator.php create mode 100644 vendor/ramsey/uuid/src/Generator/OpenSslGenerator.php create mode 100644 vendor/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php create mode 100644 vendor/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php create mode 100644 vendor/ramsey/uuid/src/Generator/RandomBytesGenerator.php create mode 100644 vendor/ramsey/uuid/src/Generator/RandomGeneratorFactory.php create mode 100644 vendor/ramsey/uuid/src/Generator/RandomGeneratorInterface.php create mode 100644 vendor/ramsey/uuid/src/Generator/RandomLibAdapter.php create mode 100644 vendor/ramsey/uuid/src/Generator/SodiumRandomGenerator.php create mode 100644 vendor/ramsey/uuid/src/Generator/TimeGeneratorFactory.php create mode 100644 vendor/ramsey/uuid/src/Generator/TimeGeneratorInterface.php create mode 100644 vendor/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php create mode 100644 vendor/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php create mode 100644 vendor/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php create mode 100644 vendor/ramsey/uuid/src/Provider/NodeProviderInterface.php create mode 100644 vendor/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php create mode 100644 vendor/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php create mode 100644 vendor/ramsey/uuid/src/Provider/TimeProviderInterface.php create mode 100644 vendor/ramsey/uuid/src/Uuid.php create mode 100644 vendor/ramsey/uuid/src/UuidFactory.php create mode 100644 vendor/ramsey/uuid/src/UuidFactoryInterface.php create mode 100644 vendor/ramsey/uuid/src/UuidInterface.php create mode 100644 vendor/ramsey/uuid/src/functions.php create mode 100644 vendor/services.php create mode 100644 vendor/swoole/ide-helper/.gitignore create mode 100644 vendor/swoole/ide-helper/.travis.yml create mode 100644 vendor/swoole/ide-helper/LICENSE create mode 100644 vendor/swoole/ide-helper/README.md create mode 100644 vendor/swoole/ide-helper/bin/generator.php create mode 100644 vendor/swoole/ide-helper/bin/generator.sh create mode 100644 vendor/swoole/ide-helper/composer.json create mode 100644 vendor/swoole/ide-helper/config/chinese/server/method/send.php create mode 100644 vendor/swoole/ide-helper/output/swoole/aliases.php create mode 100644 vendor/swoole/ide-helper/output/swoole/constants.php create mode 100644 vendor/swoole/ide-helper/output/swoole/functions.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Atomic.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Atomic/Long.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Buffer.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Client.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Connection/Iterator.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Channel.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Client.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Context.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Http/Client.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Http/Client/Exception.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Http/Server.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Http2/Client.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Http2/Client/Exception.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Iterator.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/MySQL.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/MySQL/Exception.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/MySQL/Statement.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Redis.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Scheduler.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Socket.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/Socket/Exception.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Coroutine/System.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Error.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Event.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Exception.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/ExitException.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Http/Request.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Http/Response.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Http/Server.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Http2/Request.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Http2/Response.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Lock.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Process.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Process/Pool.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Redis/Server.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Runtime.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Server.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Server/Port.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Server/Task.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Table.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Table/Row.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Timer.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/Timer/Iterator.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/WebSocket/CloseFrame.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/WebSocket/Frame.php create mode 100644 vendor/swoole/ide-helper/output/swoole/namespace/WebSocket/Server.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/aliases.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/functions.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/Async.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/Async/Client.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/Async/http/client.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/Async/mysql.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/Async/redis.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/Channel.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/Http/Client.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/Memory/Pool.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/Memory/Pool/Slice.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/Mmap.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/MsgQueue.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/MySQL.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/MySQL/Exception.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/Redis.php create mode 100644 vendor/swoole/ide-helper/output/swoole_async/namespace/RingQueue.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/alias.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/alias_ns.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/constants.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/core/ArrayObject.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/core/Constant.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/ObjectPool.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/Server.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/Server/Connection.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/WaitGroup.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/core/Curl/Exception.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/core/Curl/Handler.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/core/Http/StatusCode.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/core/StringObject.php create mode 100644 vendor/swoole/ide-helper/output/swoole_lib/functions.php create mode 100644 vendor/swoole/ide-helper/output/swoole_orm/aliases.php create mode 100644 vendor/swoole/ide-helper/output/swoole_orm/functions.php create mode 100644 vendor/swoole/ide-helper/output/swoole_postgresql/aliases.php create mode 100644 vendor/swoole/ide-helper/output/swoole_postgresql/constants.php create mode 100644 vendor/swoole/ide-helper/output/swoole_postgresql/namespace/Coroutine/PostgreSQL.php create mode 100644 vendor/swoole/ide-helper/output/swoole_serialize/aliases.php create mode 100644 vendor/swoole/ide-helper/output/swoole_serialize/constants.php create mode 100644 vendor/swoole/ide-helper/output/swoole_serialize/namespace/Serialize.php create mode 100644 vendor/swoole/ide-helper/output/swoole_zookeeper/aliases.php create mode 100644 vendor/swoole/ide-helper/output/swoole_zookeeper/constants.php create mode 100644 vendor/swoole/ide-helper/output/swoole_zookeeper/namespace/zookeeper.php create mode 100644 vendor/swoole/ide-helper/src/AbstractStubGenerator.php create mode 100644 vendor/swoole/ide-helper/src/Constant.php create mode 100644 vendor/swoole/ide-helper/src/Exception.php create mode 100644 vendor/swoole/ide-helper/src/Rules/AbstractRule.php create mode 100644 vendor/swoole/ide-helper/src/Rules/NamespaceRule.php create mode 100644 vendor/swoole/ide-helper/src/StubGenerators/Swoole.php create mode 100644 vendor/swoole/ide-helper/src/StubGenerators/SwooleAsync.php create mode 100644 vendor/swoole/ide-helper/src/StubGenerators/SwooleLib.php create mode 100644 vendor/swoole/ide-helper/src/StubGenerators/SwooleOrm.php create mode 100644 vendor/swoole/ide-helper/src/StubGenerators/SwoolePostgresql.php create mode 100644 vendor/swoole/ide-helper/src/StubGenerators/SwooleSerialize.php create mode 100644 vendor/swoole/ide-helper/src/StubGenerators/SwooleZookeeper.php create mode 100644 vendor/symfony/finder/.gitattributes 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/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/polyfill-ctype/Ctype.php create mode 100644 vendor/symfony/polyfill-ctype/LICENSE create mode 100644 vendor/symfony/polyfill-ctype/README.md create mode 100644 vendor/symfony/polyfill-ctype/bootstrap.php create mode 100644 vendor/symfony/polyfill-ctype/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/composer.json create mode 100644 vendor/topthink/framework/.gitignore create mode 100644 vendor/topthink/framework/.travis.yml create mode 100644 vendor/topthink/framework/CONTRIBUTING.md create mode 100644 vendor/topthink/framework/LICENSE.txt create mode 100644 vendor/topthink/framework/README.md create mode 100644 vendor/topthink/framework/composer.json create mode 100644 vendor/topthink/framework/logo.png create mode 100644 vendor/topthink/framework/phpunit.xml.dist create mode 100644 vendor/topthink/framework/src/helper.php create mode 100644 vendor/topthink/framework/src/lang/zh-cn.php create mode 100644 vendor/topthink/framework/src/think/App.php create mode 100644 vendor/topthink/framework/src/think/Cache.php create mode 100644 vendor/topthink/framework/src/think/Config.php create mode 100644 vendor/topthink/framework/src/think/Console.php create mode 100644 vendor/topthink/framework/src/think/Container.php create mode 100644 vendor/topthink/framework/src/think/Cookie.php create mode 100644 vendor/topthink/framework/src/think/Db.php create mode 100644 vendor/topthink/framework/src/think/Env.php create mode 100644 vendor/topthink/framework/src/think/Event.php create mode 100644 vendor/topthink/framework/src/think/Exception.php create mode 100644 vendor/topthink/framework/src/think/Facade.php create mode 100644 vendor/topthink/framework/src/think/File.php create mode 100644 vendor/topthink/framework/src/think/Filesystem.php create mode 100644 vendor/topthink/framework/src/think/Http.php create mode 100644 vendor/topthink/framework/src/think/Lang.php create mode 100644 vendor/topthink/framework/src/think/Log.php create mode 100644 vendor/topthink/framework/src/think/Manager.php create mode 100644 vendor/topthink/framework/src/think/Middleware.php create mode 100644 vendor/topthink/framework/src/think/Pipeline.php create mode 100644 vendor/topthink/framework/src/think/Request.php create mode 100644 vendor/topthink/framework/src/think/Response.php create mode 100644 vendor/topthink/framework/src/think/Route.php create mode 100644 vendor/topthink/framework/src/think/Service.php create mode 100644 vendor/topthink/framework/src/think/Session.php create mode 100644 vendor/topthink/framework/src/think/Validate.php create mode 100644 vendor/topthink/framework/src/think/View.php create mode 100644 vendor/topthink/framework/src/think/cache/Driver.php create mode 100644 vendor/topthink/framework/src/think/cache/TagSet.php create mode 100644 vendor/topthink/framework/src/think/cache/driver/File.php create mode 100644 vendor/topthink/framework/src/think/cache/driver/Memcache.php create mode 100644 vendor/topthink/framework/src/think/cache/driver/Memcached.php create mode 100644 vendor/topthink/framework/src/think/cache/driver/Redis.php create mode 100644 vendor/topthink/framework/src/think/cache/driver/Wincache.php create mode 100644 vendor/topthink/framework/src/think/console/Command.php create mode 100644 vendor/topthink/framework/src/think/console/Input.php create mode 100644 vendor/topthink/framework/src/think/console/LICENSE create mode 100644 vendor/topthink/framework/src/think/console/Output.php create mode 100644 vendor/topthink/framework/src/think/console/Table.php create mode 100644 vendor/topthink/framework/src/think/console/bin/README.md create mode 100644 vendor/topthink/framework/src/think/console/bin/hiddeninput.exe create mode 100644 vendor/topthink/framework/src/think/console/command/Clear.php create mode 100644 vendor/topthink/framework/src/think/console/command/Help.php create mode 100644 vendor/topthink/framework/src/think/console/command/Lists.php create mode 100644 vendor/topthink/framework/src/think/console/command/Make.php create mode 100644 vendor/topthink/framework/src/think/console/command/RouteList.php create mode 100644 vendor/topthink/framework/src/think/console/command/RunServer.php create mode 100644 vendor/topthink/framework/src/think/console/command/ServiceDiscover.php create mode 100644 vendor/topthink/framework/src/think/console/command/VendorPublish.php create mode 100644 vendor/topthink/framework/src/think/console/command/Version.php create mode 100644 vendor/topthink/framework/src/think/console/command/make/Command.php create mode 100644 vendor/topthink/framework/src/think/console/command/make/Controller.php create mode 100644 vendor/topthink/framework/src/think/console/command/make/Event.php create mode 100644 vendor/topthink/framework/src/think/console/command/make/Listener.php create mode 100644 vendor/topthink/framework/src/think/console/command/make/Middleware.php create mode 100644 vendor/topthink/framework/src/think/console/command/make/Model.php create mode 100644 vendor/topthink/framework/src/think/console/command/make/Service.php create mode 100644 vendor/topthink/framework/src/think/console/command/make/Subscribe.php create mode 100644 vendor/topthink/framework/src/think/console/command/make/Validate.php create mode 100644 vendor/topthink/framework/src/think/console/command/make/stubs/command.stub create mode 100644 vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub create mode 100644 vendor/topthink/framework/src/think/console/command/make/stubs/controller.plain.stub create mode 100644 vendor/topthink/framework/src/think/console/command/make/stubs/controller.stub create mode 100644 vendor/topthink/framework/src/think/console/command/make/stubs/event.stub create mode 100644 vendor/topthink/framework/src/think/console/command/make/stubs/listener.stub create mode 100644 vendor/topthink/framework/src/think/console/command/make/stubs/middleware.stub create mode 100644 vendor/topthink/framework/src/think/console/command/make/stubs/model.stub create mode 100644 vendor/topthink/framework/src/think/console/command/make/stubs/service.stub create mode 100644 vendor/topthink/framework/src/think/console/command/make/stubs/subscribe.stub create mode 100644 vendor/topthink/framework/src/think/console/command/make/stubs/validate.stub create mode 100644 vendor/topthink/framework/src/think/console/command/optimize/Route.php create mode 100644 vendor/topthink/framework/src/think/console/command/optimize/Schema.php create mode 100644 vendor/topthink/framework/src/think/console/input/Argument.php create mode 100644 vendor/topthink/framework/src/think/console/input/Definition.php create mode 100644 vendor/topthink/framework/src/think/console/input/Option.php create mode 100644 vendor/topthink/framework/src/think/console/output/Ask.php create mode 100644 vendor/topthink/framework/src/think/console/output/Descriptor.php create mode 100644 vendor/topthink/framework/src/think/console/output/Formatter.php create mode 100644 vendor/topthink/framework/src/think/console/output/Question.php create mode 100644 vendor/topthink/framework/src/think/console/output/descriptor/Console.php create mode 100644 vendor/topthink/framework/src/think/console/output/driver/Buffer.php create mode 100644 vendor/topthink/framework/src/think/console/output/driver/Console.php create mode 100644 vendor/topthink/framework/src/think/console/output/driver/Nothing.php create mode 100644 vendor/topthink/framework/src/think/console/output/formatter/Stack.php create mode 100644 vendor/topthink/framework/src/think/console/output/formatter/Style.php create mode 100644 vendor/topthink/framework/src/think/console/output/question/Choice.php create mode 100644 vendor/topthink/framework/src/think/console/output/question/Confirmation.php create mode 100644 vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php create mode 100644 vendor/topthink/framework/src/think/contract/LogHandlerInterface.php create mode 100644 vendor/topthink/framework/src/think/contract/ModelRelationInterface.php create mode 100644 vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php create mode 100644 vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php create mode 100644 vendor/topthink/framework/src/think/event/AppInit.php create mode 100644 vendor/topthink/framework/src/think/event/HttpEnd.php create mode 100644 vendor/topthink/framework/src/think/event/HttpRun.php create mode 100644 vendor/topthink/framework/src/think/event/LogWrite.php create mode 100644 vendor/topthink/framework/src/think/event/RouteLoaded.php create mode 100644 vendor/topthink/framework/src/think/exception/ClassNotFoundException.php create mode 100644 vendor/topthink/framework/src/think/exception/ErrorException.php create mode 100644 vendor/topthink/framework/src/think/exception/FileException.php create mode 100644 vendor/topthink/framework/src/think/exception/FuncNotFoundException.php create mode 100644 vendor/topthink/framework/src/think/exception/Handle.php create mode 100644 vendor/topthink/framework/src/think/exception/HttpException.php create mode 100644 vendor/topthink/framework/src/think/exception/HttpResponseException.php create mode 100644 vendor/topthink/framework/src/think/exception/InvalidArgumentException.php create mode 100644 vendor/topthink/framework/src/think/exception/RouteNotFoundException.php create mode 100644 vendor/topthink/framework/src/think/exception/ValidateException.php create mode 100644 vendor/topthink/framework/src/think/facade/App.php create mode 100644 vendor/topthink/framework/src/think/facade/Cache.php create mode 100644 vendor/topthink/framework/src/think/facade/Config.php create mode 100644 vendor/topthink/framework/src/think/facade/Console.php create mode 100644 vendor/topthink/framework/src/think/facade/Cookie.php create mode 100644 vendor/topthink/framework/src/think/facade/Env.php create mode 100644 vendor/topthink/framework/src/think/facade/Event.php create mode 100644 vendor/topthink/framework/src/think/facade/Filesystem.php create mode 100644 vendor/topthink/framework/src/think/facade/Lang.php create mode 100644 vendor/topthink/framework/src/think/facade/Log.php create mode 100644 vendor/topthink/framework/src/think/facade/Middleware.php create mode 100644 vendor/topthink/framework/src/think/facade/Request.php create mode 100644 vendor/topthink/framework/src/think/facade/Route.php create mode 100644 vendor/topthink/framework/src/think/facade/Session.php create mode 100644 vendor/topthink/framework/src/think/facade/Validate.php create mode 100644 vendor/topthink/framework/src/think/facade/View.php create mode 100644 vendor/topthink/framework/src/think/file/UploadedFile.php create mode 100644 vendor/topthink/framework/src/think/filesystem/CacheStore.php create mode 100644 vendor/topthink/framework/src/think/filesystem/Driver.php create mode 100644 vendor/topthink/framework/src/think/filesystem/driver/Local.php create mode 100644 vendor/topthink/framework/src/think/initializer/BootService.php create mode 100644 vendor/topthink/framework/src/think/initializer/Error.php create mode 100644 vendor/topthink/framework/src/think/initializer/RegisterService.php create mode 100644 vendor/topthink/framework/src/think/log/Channel.php create mode 100644 vendor/topthink/framework/src/think/log/ChannelSet.php create mode 100644 vendor/topthink/framework/src/think/log/driver/File.php create mode 100644 vendor/topthink/framework/src/think/log/driver/Socket.php create mode 100644 vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php create mode 100644 vendor/topthink/framework/src/think/middleware/CheckRequestCache.php create mode 100644 vendor/topthink/framework/src/think/middleware/FormTokenCheck.php create mode 100644 vendor/topthink/framework/src/think/middleware/LoadLangPack.php create mode 100644 vendor/topthink/framework/src/think/middleware/SessionInit.php create mode 100644 vendor/topthink/framework/src/think/response/File.php create mode 100644 vendor/topthink/framework/src/think/response/Html.php create mode 100644 vendor/topthink/framework/src/think/response/Json.php create mode 100644 vendor/topthink/framework/src/think/response/Jsonp.php create mode 100644 vendor/topthink/framework/src/think/response/Redirect.php create mode 100644 vendor/topthink/framework/src/think/response/View.php create mode 100644 vendor/topthink/framework/src/think/response/Xml.php create mode 100644 vendor/topthink/framework/src/think/route/Dispatch.php create mode 100644 vendor/topthink/framework/src/think/route/Domain.php create mode 100644 vendor/topthink/framework/src/think/route/Resource.php create mode 100644 vendor/topthink/framework/src/think/route/Rule.php create mode 100644 vendor/topthink/framework/src/think/route/RuleGroup.php create mode 100644 vendor/topthink/framework/src/think/route/RuleItem.php create mode 100644 vendor/topthink/framework/src/think/route/RuleName.php create mode 100644 vendor/topthink/framework/src/think/route/Url.php create mode 100644 vendor/topthink/framework/src/think/route/dispatch/Callback.php create mode 100644 vendor/topthink/framework/src/think/route/dispatch/Controller.php create mode 100644 vendor/topthink/framework/src/think/route/dispatch/Redirect.php create mode 100644 vendor/topthink/framework/src/think/route/dispatch/Response.php create mode 100644 vendor/topthink/framework/src/think/route/dispatch/Url.php create mode 100644 vendor/topthink/framework/src/think/route/dispatch/View.php create mode 100644 vendor/topthink/framework/src/think/service/ModelService.php create mode 100644 vendor/topthink/framework/src/think/service/PaginatorService.php create mode 100644 vendor/topthink/framework/src/think/service/ValidateService.php create mode 100644 vendor/topthink/framework/src/think/session/Store.php create mode 100644 vendor/topthink/framework/src/think/session/driver/Cache.php create mode 100644 vendor/topthink/framework/src/think/session/driver/File.php create mode 100644 vendor/topthink/framework/src/think/validate/ValidateRule.php create mode 100644 vendor/topthink/framework/src/think/view/driver/Php.php create mode 100644 vendor/topthink/framework/src/tpl/think_exception.tpl create mode 100644 vendor/topthink/framework/tests/AppTest.php create mode 100644 vendor/topthink/framework/tests/CacheTest.php create mode 100644 vendor/topthink/framework/tests/ConfigTest.php create mode 100644 vendor/topthink/framework/tests/ContainerTest.php create mode 100644 vendor/topthink/framework/tests/DbTest.php create mode 100644 vendor/topthink/framework/tests/EnvTest.php create mode 100644 vendor/topthink/framework/tests/EventTest.php create mode 100644 vendor/topthink/framework/tests/FilesystemTest.php create mode 100644 vendor/topthink/framework/tests/HttpTest.php create mode 100644 vendor/topthink/framework/tests/LogTest.php create mode 100644 vendor/topthink/framework/tests/MiddlewareTest.php create mode 100644 vendor/topthink/framework/tests/SessionTest.php create mode 100644 vendor/topthink/framework/tests/ViewTest.php create mode 100644 vendor/topthink/framework/tests/bootstrap.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/Collection.php create mode 100644 vendor/topthink/think-helper/src/contract/Arrayable.php create mode 100644 vendor/topthink/think-helper/src/contract/Jsonable.php create mode 100644 vendor/topthink/think-helper/src/helper.php create mode 100644 vendor/topthink/think-helper/src/helper/Arr.php create mode 100644 vendor/topthink/think-helper/src/helper/Str.php create mode 100644 vendor/topthink/think-multi-app/LICENSE create mode 100644 vendor/topthink/think-multi-app/README.md create mode 100644 vendor/topthink/think-multi-app/composer.json create mode 100644 vendor/topthink/think-multi-app/src/MultiApp.php create mode 100644 vendor/topthink/think-multi-app/src/Service.php create mode 100644 vendor/topthink/think-multi-app/src/Url.php create mode 100644 vendor/topthink/think-multi-app/src/command/Build.php create mode 100644 vendor/topthink/think-multi-app/src/command/stubs/controller.stub create mode 100644 vendor/topthink/think-orm/.gitignore create mode 100644 vendor/topthink/think-orm/LICENSE create mode 100644 vendor/topthink/think-orm/README.md create mode 100644 vendor/topthink/think-orm/composer.json create mode 100644 vendor/topthink/think-orm/src/DbManager.php create mode 100644 vendor/topthink/think-orm/src/Model.php create mode 100644 vendor/topthink/think-orm/src/Paginator.php create mode 100644 vendor/topthink/think-orm/src/db/BaseQuery.php create mode 100644 vendor/topthink/think-orm/src/db/Builder.php create mode 100644 vendor/topthink/think-orm/src/db/CacheItem.php create mode 100644 vendor/topthink/think-orm/src/db/Connection.php create mode 100644 vendor/topthink/think-orm/src/db/ConnectionInterface.php create mode 100644 vendor/topthink/think-orm/src/db/Fetch.php create mode 100644 vendor/topthink/think-orm/src/db/Mongo.php create mode 100644 vendor/topthink/think-orm/src/db/PDOConnection.php create mode 100644 vendor/topthink/think-orm/src/db/Query.php create mode 100644 vendor/topthink/think-orm/src/db/Raw.php create mode 100644 vendor/topthink/think-orm/src/db/Where.php create mode 100644 vendor/topthink/think-orm/src/db/builder/Mongo.php create mode 100644 vendor/topthink/think-orm/src/db/builder/Mysql.php create mode 100644 vendor/topthink/think-orm/src/db/builder/Oracle.php create mode 100644 vendor/topthink/think-orm/src/db/builder/Pgsql.php create mode 100644 vendor/topthink/think-orm/src/db/builder/Sqlite.php create mode 100644 vendor/topthink/think-orm/src/db/builder/Sqlsrv.php create mode 100644 vendor/topthink/think-orm/src/db/concern/AggregateQuery.php create mode 100644 vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php create mode 100644 vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php create mode 100644 vendor/topthink/think-orm/src/db/concern/ParamsBind.php create mode 100644 vendor/topthink/think-orm/src/db/concern/ResultOperation.php create mode 100644 vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php create mode 100644 vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php create mode 100644 vendor/topthink/think-orm/src/db/concern/Transaction.php create mode 100644 vendor/topthink/think-orm/src/db/concern/WhereQuery.php create mode 100644 vendor/topthink/think-orm/src/db/connector/Mongo.php create mode 100644 vendor/topthink/think-orm/src/db/connector/Mysql.php create mode 100644 vendor/topthink/think-orm/src/db/connector/Oracle.php create mode 100644 vendor/topthink/think-orm/src/db/connector/Pgsql.php create mode 100644 vendor/topthink/think-orm/src/db/connector/Sqlite.php create mode 100644 vendor/topthink/think-orm/src/db/connector/Sqlsrv.php create mode 100644 vendor/topthink/think-orm/src/db/connector/pgsql.sql create mode 100644 vendor/topthink/think-orm/src/db/exception/BindParamException.php create mode 100644 vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php create mode 100644 vendor/topthink/think-orm/src/db/exception/DbException.php create mode 100644 vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php create mode 100644 vendor/topthink/think-orm/src/db/exception/ModelEventException.php create mode 100644 vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php create mode 100644 vendor/topthink/think-orm/src/db/exception/PDOException.php create mode 100644 vendor/topthink/think-orm/src/facade/Db.php create mode 100644 vendor/topthink/think-orm/src/model/Collection.php create mode 100644 vendor/topthink/think-orm/src/model/Pivot.php create mode 100644 vendor/topthink/think-orm/src/model/Relation.php create mode 100644 vendor/topthink/think-orm/src/model/concern/Attribute.php create mode 100644 vendor/topthink/think-orm/src/model/concern/Conversion.php create mode 100644 vendor/topthink/think-orm/src/model/concern/ModelEvent.php create mode 100644 vendor/topthink/think-orm/src/model/concern/OptimLock.php create mode 100644 vendor/topthink/think-orm/src/model/concern/RelationShip.php create mode 100644 vendor/topthink/think-orm/src/model/concern/SoftDelete.php create mode 100644 vendor/topthink/think-orm/src/model/concern/TimeStamp.php create mode 100644 vendor/topthink/think-orm/src/model/relation/BelongsTo.php create mode 100644 vendor/topthink/think-orm/src/model/relation/BelongsToMany.php create mode 100644 vendor/topthink/think-orm/src/model/relation/HasMany.php create mode 100644 vendor/topthink/think-orm/src/model/relation/HasManyThrough.php create mode 100644 vendor/topthink/think-orm/src/model/relation/HasOne.php create mode 100644 vendor/topthink/think-orm/src/model/relation/HasOneThrough.php create mode 100644 vendor/topthink/think-orm/src/model/relation/MorphMany.php create mode 100644 vendor/topthink/think-orm/src/model/relation/MorphOne.php create mode 100644 vendor/topthink/think-orm/src/model/relation/MorphTo.php create mode 100644 vendor/topthink/think-orm/src/model/relation/OneToOne.php create mode 100644 vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php create mode 100644 vendor/topthink/think-socketlog/LICENSE create mode 100644 vendor/topthink/think-socketlog/README.md create mode 100644 vendor/topthink/think-socketlog/composer.json create mode 100644 vendor/topthink/think-socketlog/src/SocketLog.php create mode 100644 vendor/topthink/think-swoole/.gitignore create mode 100644 vendor/topthink/think-swoole/LICENSE create mode 100644 vendor/topthink/think-swoole/README.md create mode 100644 vendor/topthink/think-swoole/composer.json create mode 100644 vendor/topthink/think-swoole/src/App.php create mode 100644 vendor/topthink/think-swoole/src/FileWatcher.php create mode 100644 vendor/topthink/think-swoole/src/Http.php create mode 100644 vendor/topthink/think-swoole/src/Manager.php create mode 100644 vendor/topthink/think-swoole/src/PidManager.php create mode 100644 vendor/topthink/think-swoole/src/Sandbox.php create mode 100644 vendor/topthink/think-swoole/src/Service.php create mode 100644 vendor/topthink/think-swoole/src/Table.php create mode 100644 vendor/topthink/think-swoole/src/Websocket.php create mode 100644 vendor/topthink/think-swoole/src/command/RpcInterface.php create mode 100644 vendor/topthink/think-swoole/src/command/Server.php create mode 100644 vendor/topthink/think-swoole/src/concerns/InteractsWithHttp.php create mode 100644 vendor/topthink/think-swoole/src/concerns/InteractsWithPool.php create mode 100644 vendor/topthink/think-swoole/src/concerns/InteractsWithPoolConnector.php create mode 100644 vendor/topthink/think-swoole/src/concerns/InteractsWithRpc.php create mode 100644 vendor/topthink/think-swoole/src/concerns/InteractsWithServer.php create mode 100644 vendor/topthink/think-swoole/src/concerns/InteractsWithSwooleTable.php create mode 100644 vendor/topthink/think-swoole/src/concerns/InteractsWithWebsocket.php create mode 100644 vendor/topthink/think-swoole/src/config/swoole.php create mode 100644 vendor/topthink/think-swoole/src/contract/ResetterInterface.php create mode 100644 vendor/topthink/think-swoole/src/contract/rpc/ParserInterface.php create mode 100644 vendor/topthink/think-swoole/src/contract/websocket/HandlerInterface.php create mode 100644 vendor/topthink/think-swoole/src/contract/websocket/ParserInterface.php create mode 100644 vendor/topthink/think-swoole/src/contract/websocket/RoomInterface.php create mode 100644 vendor/topthink/think-swoole/src/coroutine/Context.php create mode 100644 vendor/topthink/think-swoole/src/exception/RpcClientException.php create mode 100644 vendor/topthink/think-swoole/src/exception/RpcResponseException.php create mode 100644 vendor/topthink/think-swoole/src/facade/Server.php create mode 100644 vendor/topthink/think-swoole/src/helpers.php create mode 100644 vendor/topthink/think-swoole/src/middleware/ResetVarDumper.php create mode 100644 vendor/topthink/think-swoole/src/pool/Cache.php create mode 100644 vendor/topthink/think-swoole/src/pool/Db.php create mode 100644 vendor/topthink/think-swoole/src/pool/cache/Store.php create mode 100644 vendor/topthink/think-swoole/src/pool/db/Connection.php create mode 100644 vendor/topthink/think-swoole/src/resetters/ClearInstances.php create mode 100644 vendor/topthink/think-swoole/src/resetters/ResetConfig.php create mode 100644 vendor/topthink/think-swoole/src/resetters/ResetEvent.php create mode 100644 vendor/topthink/think-swoole/src/resetters/ResetService.php create mode 100644 vendor/topthink/think-swoole/src/rpc/Error.php create mode 100644 vendor/topthink/think-swoole/src/rpc/JsonParser.php create mode 100644 vendor/topthink/think-swoole/src/rpc/Protocol.php create mode 100644 vendor/topthink/think-swoole/src/rpc/client/Client.php create mode 100644 vendor/topthink/think-swoole/src/rpc/client/Connection.php create mode 100644 vendor/topthink/think-swoole/src/rpc/client/Pool.php create mode 100644 vendor/topthink/think-swoole/src/rpc/client/Proxy.php create mode 100644 vendor/topthink/think-swoole/src/rpc/server/Dispatcher.php create mode 100644 vendor/topthink/think-swoole/src/websocket/Pusher.php create mode 100644 vendor/topthink/think-swoole/src/websocket/Room.php create mode 100644 vendor/topthink/think-swoole/src/websocket/SimpleParser.php create mode 100644 vendor/topthink/think-swoole/src/websocket/room/Redis.php create mode 100644 vendor/topthink/think-swoole/src/websocket/room/Table.php create mode 100644 vendor/topthink/think-swoole/src/websocket/socketio/Controller.php create mode 100644 vendor/topthink/think-swoole/src/websocket/socketio/Handler.php create mode 100644 vendor/topthink/think-swoole/src/websocket/socketio/Packet.php create mode 100644 vendor/topthink/think-swoole/src/websocket/socketio/Parser.php create mode 100644 vendor/topthink/think-template/.gitignore create mode 100644 vendor/topthink/think-template/LICENSE create mode 100644 vendor/topthink/think-template/README.md create mode 100644 vendor/topthink/think-template/composer.json create mode 100644 vendor/topthink/think-template/src/Template.php create mode 100644 vendor/topthink/think-template/src/facade/Template.php create mode 100644 vendor/topthink/think-template/src/template/TagLib.php create mode 100644 vendor/topthink/think-template/src/template/driver/File.php create mode 100644 vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php create mode 100644 vendor/topthink/think-template/src/template/taglib/Cx.php create mode 100644 vendor/topthink/think-view/.gitignore create mode 100644 vendor/topthink/think-view/LICENSE create mode 100644 vendor/topthink/think-view/README.md create mode 100644 vendor/topthink/think-view/composer.json create mode 100644 vendor/topthink/think-view/src/Think.php create mode 100644 view/massage/tcp/game_list copy.html create mode 100644 view/massage/tcp/game_list.html create mode 100644 web/.babelrc create mode 100644 web/.editorconfig create mode 100644 web/.eslintignore create mode 100644 web/.eslintrc.js create mode 100644 web/.gitignore create mode 100644 web/.postcssrc.js create mode 100644 web/README.md create mode 100644 web/build/build.js create mode 100644 web/build/check-versions.js create mode 100644 web/build/logo.png create mode 100644 web/build/utils.js create mode 100644 web/build/vue-loader.conf.js create mode 100644 web/build/webpack.base.conf.js create mode 100644 web/build/webpack.dev.conf.js create mode 100644 web/build/webpack.prod.conf.js create mode 100644 web/config/dev.env.js create mode 100644 web/config/index.js create mode 100644 web/config/prod.env.js create mode 100644 web/index.html create mode 100644 web/package-lock.json create mode 100644 web/package.json create mode 100644 web/runtime/services.php create mode 100644 web/src/App.vue create mode 100644 web/src/api/index.js create mode 100644 web/src/api/modules/businessCard.js create mode 100644 web/src/api/modules/index.js create mode 100644 web/src/api/modules/survey.js create mode 100644 web/src/assets/logo.png create mode 100644 web/src/components/ad.vue create mode 100644 web/src/components/basics/index.js create mode 100644 web/src/components/basics/lbButton.vue create mode 100644 web/src/components/basics/lbPage.vue create mode 100644 web/src/components/basics/lbSwitch.vue create mode 100644 web/src/components/basics/lbTips.vue create mode 100644 web/src/components/basics/topNav.vue create mode 100644 web/src/components/container.vue create mode 100644 web/src/components/footer.vue create mode 100644 web/src/components/header.vue create mode 100644 web/src/components/layout.vue create mode 100644 web/src/components/sidebar.vue create mode 100644 web/src/i18n/index.js create mode 100644 web/src/i18n/langs/en.json create mode 100644 web/src/i18n/langs/zh.json create mode 100644 web/src/main.js create mode 100644 web/src/permission.js create mode 100644 web/src/router/_import_development.js create mode 100644 web/src/router/_import_production.js create mode 100644 web/src/router/index.js create mode 100644 web/src/router/test.js create mode 100644 web/src/store/index.js create mode 100644 web/src/store/modules/operate.js create mode 100644 web/src/store/modules/routes.js create mode 100644 web/src/style/icon.css create mode 100644 web/src/style/reset.css create mode 100644 web/src/style/theme.scss create mode 100644 web/src/utils/reg.js create mode 100644 web/src/view/404/index.vue create mode 100644 web/src/view/businessCard/manage/index.vue create mode 100644 web/src/view/businessCard/manage/subPage/cardMange.vue create mode 100644 web/src/view/businessCard/manage/subPage/defaultContent.vue create mode 100644 web/src/view/businessCard/manage/tag.vue create mode 100644 web/src/view/businessCard/set/clientSet.vue create mode 100644 web/src/view/businessCard/set/mobileSet.vue create mode 100644 web/src/view/dynamic/comment.vue create mode 100644 web/src/view/dynamic/manage.vue create mode 100644 web/src/view/login/index.vue create mode 100644 web/src/view/malls/goods/classify.vue create mode 100644 web/src/view/malls/goods/list.vue create mode 100644 web/src/view/malls/order/manage.vue create mode 100644 web/src/view/malls/order/refund.vue create mode 100644 web/src/view/malls/set/deal.vue create mode 100644 web/src/view/malls/set/payment.vue create mode 100644 web/src/view/malls/set/staffGoods.vue create mode 100644 web/src/view/malls/set/subpage/overTime.vue create mode 100644 web/src/view/malls/set/subpage/pickUp.vue create mode 100644 web/src/view/survey/index.vue create mode 100644 web/src/view/website/case/case.vue create mode 100644 web/src/view/website/case/manage.vue create mode 100644 web/src/view/website/news/list.vue create mode 100644 web/src/view/website/news/manage.vue create mode 100644 web/src/view/website/other/about.vue create mode 100644 web/src/view/website/other/business.vue create mode 100644 web/src/view/website/other/history.vue create mode 100644 web/src/view/website/other/recruit.vue create mode 100644 web/src/view/website/product/list.vue create mode 100644 web/src/view/website/product/manage.vue create mode 100644 web/static/.gitkeep create mode 100644 weixinpay/doc/README create mode 100644 weixinpay/doc/README.doc create mode 100644 weixinpay/example/WxPay.JsApiPay.php create mode 100644 weixinpay/example/WxPay.MicroPay.php create mode 100644 weixinpay/example/WxPay.NativePay.php create mode 100644 weixinpay/example/download.php create mode 100644 weixinpay/example/jsapi.php create mode 100644 weixinpay/example/log.php create mode 100644 weixinpay/example/micropay.php create mode 100644 weixinpay/example/native.php create mode 100644 weixinpay/example/native_notify.php create mode 100644 weixinpay/example/notify.php create mode 100644 weixinpay/example/orderquery.php create mode 100644 weixinpay/example/phpqrcode/phpqrcode.php create mode 100644 weixinpay/example/qrcode.php create mode 100644 weixinpay/example/refund.php create mode 100644 weixinpay/example/refundquery.php create mode 100644 weixinpay/image/bk.png create mode 100644 weixinpay/image/image001.jpg create mode 100644 weixinpay/image/image002.png create mode 100644 weixinpay/lib/WechatAppPay.php create mode 100644 weixinpay/lib/WxMchPay.php create mode 100644 weixinpay/lib/WxOrderNotify.php create mode 100644 weixinpay/lib/WxPay.Api.php create mode 100644 weixinpay/lib/WxPay.Config.php create mode 100644 weixinpay/lib/WxPay.Data.php create mode 100644 weixinpay/lib/WxPay.Exception.php create mode 100644 weixinpay/lib/WxPay.Notify.php diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..574a39c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/Schema/AdminAdminCreateUserRequest.json b/Schema/AdminAdminCreateUserRequest.json new file mode 100644 index 0000000..be86bac --- /dev/null +++ b/Schema/AdminAdminCreateUserRequest.json @@ -0,0 +1,79 @@ +{ + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "account": { + "type": "string", + "minLength": 1, + "maxLength": 32 + }, + "passwd": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 6, + "maxLength": 32 + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 32 + }, + "department_id": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 32, + "maxLength": 32 + }, + "status": { + "type": "integer" + }, + "certificate_type": { + "type": "integer" + }, + "certificate_num": { + "type": "string" + "minLength": 1, + "maxLength": 32 + }, + "nickname": { + "type": "string", + "minLength": 1, + "maxLength": 32 + }, + "wechat": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 1, + "maxLength": 100 + }, + "email": { + "type": "string", + "format": "regex", + "pattern": "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/i", + "minLength": 5, + "maxLength": 32 + }, + "qq": { + "type": "integer" + }, + "mobile": { + "type": "string", + "minLength": 8, + "maxLength": 20 + }, + "role_id": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 32, + "maxLength": 32 + } + } + } + } +} \ No newline at end of file diff --git a/Schema/AdminAdminUpdateUserRequest.json b/Schema/AdminAdminUpdateUserRequest.json new file mode 100644 index 0000000..52b7a5d --- /dev/null +++ b/Schema/AdminAdminUpdateUserRequest.json @@ -0,0 +1,87 @@ +{ + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "passwd": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + }, + "name": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + }, + "department_id": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + }, + "status": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + }, + "certificate_type": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + }, + "certificate_num": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + }, + "nickname": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + }, + "wechat": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + }, + "qq": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + }, + "mobile": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + }, + "role_id": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + } + } + } + } +} \ No newline at end of file diff --git a/Schema/AdminAuthAuthRequest.json b/Schema/AdminAuthAuthRequest.json new file mode 100644 index 0000000..d9680c6 --- /dev/null +++ b/Schema/AdminAuthAuthRequest.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "account": { + "type": "string", + "format": "regex", + "pattern": "\/^[a-z0-9]+$\/i", + "minLength": 0, + "maxLength": 15 + }, + "passwd": { + "type": "string", + "format": "regex", + "pattern": "\/^[a-z0-9]+$\/i", + "minLength": 0, + "maxLength": 32 + } + } + } + } +} \ No newline at end of file diff --git a/Schema/AdminConfigSetAppConfigRequest.json b/Schema/AdminConfigSetAppConfigRequest.json new file mode 100644 index 0000000..a60a94b --- /dev/null +++ b/Schema/AdminConfigSetAppConfigRequest.json @@ -0,0 +1,416 @@ +{ + "type": "object", + "properties": { + "app_config": { + "type": "object", + "properties": { + "multi": { + "type": "integer", + "minimum": 0, + "maximum": 99999999999 + }, + "create_time": { + "type": "integer", + "minimum": 0, + "maximum": 99999999999 + }, + "update_time": { + "type": "integer", + "minimum": 0, + "maximum": 99999999999 + }, + "show_card": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "copyright": { + "type": "string", + "minLength": 0, + "maxLength": 200 + }, + "mini_template_id": { + "type": "string", + "minLength": 0, + "maxLength": 200 + }, + "mini_app_name": { + "type": "string", + "minLength": 0, + "maxLength": 30 + }, + "status": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "allow_create": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "create_text": { + "type": "string", + "minLength": 0, + "maxLength": 100 + }, + "logo_switch": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "logo_text": { + "type": "string", + "minLength": 0, + "maxLength": 100 + }, + "logo_phone": { + "type": "string", + "minLength": 0, + "maxLength": 20 + }, + "notice_switch": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "notice_i": { + "type": "integer", + "minimum": 0, + "maximum": 9999999999 + }, + "min_tmppid": { + "type": "string", + "minLength": 0, + "maxLength": 200 + }, + "order_overtime": { + "type": "integer", + "default": 1800, + "minimum": 0, + "maximum": 9999999999 + }, + "collage_overtime": { + "type": "integer", + "minimum": 0, + "maximum": 9999999999 + }, + "force_phone": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "staff_extract": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "first_extract": { + "type": "integer", + "minimum": 0, + "maximum": 3 + }, + "sec_extract": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "code": { + "type": "string", + "minLength": 0, + "maxLength": 20 + }, + "corpid": { + "type": "string", + "minLength": 0, + "maxLength": 200 + }, + "corpsecret": { + "type": "string", + "minLength": 0, + "maxLength": 200 + }, + "agentid": { + "type": "string", + "minLength": 0, + "maxLength": 200 + }, + "wx_appid": { + "type": "string", + "minLength": 0, + "maxLength": 100 + }, + "wx_tplid": { + "type": "string", + "minLength": 0, + "maxLength": 100 + }, + "redis_pas": { + "type": "string", + "minLength": 0, + "maxLength": 100 + }, + "cash_mini": { + "type": "string", + "minLength": 0, + "maxLength": 9999999999 + }, + "admin_account": { + "type": "string", + "minLength": 0, + "maxLength": 100 + }, + "plug_form": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "btn_consult": { + "type": "string", + "minLength": 0, + "maxLength": 20 + }, + "btn_sale": { + "type": "string", + "minLength": 0, + "maxLength": 20 + }, + "btn_code_err": { + "type": "string", + "minLength": 0, + "maxLength": 200 + }, + "btn_code_miss": { + "type": "string", + "minLength": 0, + "maxLength": 200 + }, + "preview_switch": { + "type": "integer", + "minimum": 0, + "maximum": 9999 + }, + "btn_talk": { + "type": "string", + "minLength": 0, + "maxLength": 20 + }, + "receiving": { + "type": "integer", + "minimum": 0, + "maximum": 9999999999 + }, + "ios_pay": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "android_pay": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "self_text": { + "type": "string", + "minLength": 0, + "maxLength": 200 + }, + "default_video": { + "type": "string", + "minLength": 0, + "maxLength": 200 + }, + "default_voice": { + "type": "string", + "minLength": 0, + "maxLength": 200 + }, + "card_type": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "order_pwd": { + "type": "string", + "minLength": 0, + "maxLength": 40 + }, + "exchange_switch": { + "type": "integer", + "minimum": 0, + "maximum": 999 + }, + "motto_switch": { + "type": "integer", + "minimum": 0, + "maximum": 999 + }, + "default_voice_switch": { + "type": "integer", + "minimum": 0, + "maximum": 999 + }, + "myshop_switch": { + "type": "integer", + "minimum": 0, + "maximum": 999 + }, + "aliyun_sms_access_key_id": { + "type": "string", + "minLength": 0, + "maxLength": 50 + }, + "aliyun_sms_access_key_secret": { + "type": "string", + "minLength": 0, + "maxLength": 50 + }, + "default_shop_name": { + "type": "string", + "minLength": 0, + "maxLength": 500 + }, + "default_shop_pic": { + "type": "string", + "minLength": 0, + "maxLength": 500 + }, + "default_video_cover": { + "type": "string", + "minLength": 0, + "maxLength": 500 + }, + "appoint_pic": { + "type": "string", + "minLength": 0, + "maxLength": 500 + }, + "appoint_name": { + "type": "string", + "minLength": 0, + "maxLength": 500 + }, + "click_copy_way": { + "type": "integer", + "minimum": 0, + "maximum": 999 + }, + "click_copy_show_img": { + "type": "string", + "minLength": 0, + "maxLength": 500 + }, + "click_copy_content": { + "type": "string", + "minLength": 0, + "maxLength": 500 + }, + "question_switch": { + "type": "integer", + "minimum": 0, + "maximum": 1 + }, + "question_text": { + "type": "string", + "minLength": 0, + "maxLength": 200 + }, + "shop_version": { + "type": "integer", + "minimum": 0, + "maximum": 999 + }, + "shop_carousel_more": { + "type": "string", + "minLength": 0, + "maxLength": 300 + }, + "exchange_btn": { + "type": "string", + "minLength": 0, + "maxLength": 40 + }, + "my_shop_limit": { + "type": "integer", + "minimum": 0, + "maximum": 9999999999 + }, + "autograph": { + "type": "integer", + "minimum": 0, + "maximum": 99999999999 + }, + "signature": { + "type": "integer", + "minimum": 0, + "maximum": 99999999999 + }, + "vr_tittle": { + "type": "string", + "minLength": 0, + "maxLength": 50 + }, + "vr_cover": { + "type": "string", + "minLength": 0, + "maxLength": 300 + }, + "vr_path": { + "type": "string", + "minLength": 0, + "maxLength": 300 + }, + "vr_switch": { + "type": "integer", + "minimum": 0, + "maximum": 999 + }, + "qr_avatar_switch": { + "type": "integer", + "minimum": 0, + "maximum": 999 + }, + "coupon_pass": { + "type": "integer", + "minimum": 0, + "maximum": 99999999999 + }, + "auto_switch": { + "type": "integer", + "minimum": 0, + "maximum": 999 + }, + "auto_switch_way": { + "type": "integer", + "minimum": 0, + "maximum": 999 + }, + "job_switch": { + "type": "integer", + "minimum": 0, + "maximum": 999 + }, + "appid": { + "type": "string", + "minLength": 0, + "maxLength": 40 + }, + "app_secret": { + "type": "string", + "minLength": 0, + "maxLength": 40 + }, + "chat_switch": { + "type": "integer", + "minimum": 0, + "maximum": 999 + }, + "auth_switch": { + "type": "integer", + "minimum": 0, + "maximum": 999 + } + } + } + } +} \ No newline at end of file diff --git a/Schema/AdminConfigSetTabbarRequest.json b/Schema/AdminConfigSetTabbarRequest.json new file mode 100644 index 0000000..fb59b08 --- /dev/null +++ b/Schema/AdminConfigSetTabbarRequest.json @@ -0,0 +1,50 @@ +{ + "type": "object", + "properties": { + "data": { + "type": "array", + "minItems": 0, + "maxItems": 20, + "items": { + "type": "object", + "properties": { + "menu_name": { + "type": "string", + "minLength": 0, + "maxLength": 32 + }, + "is_show": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1 + }, + "name": { + "type": "string", + "pattern": "\/^[a-z0-9]+$\/i", + "minLength": 0, + "maxLength": 20 + }, + "url": { + "type": "string", + "pattern": "\/^[a-z0-9]+$\/i", + "minLength": 0, + "maxLength": 500 + }, + "url_out": { + "type": "string", + "pattern": "\/^[a-z0-9]+$\/i", + "minLength": 0, + "maxLength": 500 + }, + "url_jump_way": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 999 + } + } + } + } + } +} \ No newline at end of file diff --git a/Schema/CustomerAdminAddQuestionnairRequest.json b/Schema/CustomerAdminAddQuestionnairRequest.json new file mode 100644 index 0000000..9a48f7f --- /dev/null +++ b/Schema/CustomerAdminAddQuestionnairRequest.json @@ -0,0 +1,30 @@ +{ + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "question": { + "type": "array", + "minItems": 0, + "maxItems": 200, + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 0, + "maxLength": 100 + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Schema/CustomerAdminAddRelyTypeRequest.json b/Schema/CustomerAdminAddRelyTypeRequest.json new file mode 100644 index 0000000..37f905a --- /dev/null +++ b/Schema/CustomerAdminAddRelyTypeRequest.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 20 + }, + "top": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 999999999 + } + } + } + } +} \ No newline at end of file diff --git a/Schema/CustomerAdminAddReplyRequest.json b/Schema/CustomerAdminAddReplyRequest.json new file mode 100644 index 0000000..a9b5141 --- /dev/null +++ b/Schema/CustomerAdminAddReplyRequest.json @@ -0,0 +1,27 @@ +{ + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "content": { + "type": "string", + "minLength": 0, + "maxLength": 100 + }, + "top": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 9999999999 + }, + "type": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 9999999999 + } + } + } + } +} \ No newline at end of file diff --git a/Schema/CustomerAdminSetQuestionnairConfigRequest.json b/Schema/CustomerAdminSetQuestionnairConfigRequest.json new file mode 100644 index 0000000..b034486 --- /dev/null +++ b/Schema/CustomerAdminSetQuestionnairConfigRequest.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "question_switch": { + "type": "integer", + "default": 1, + "minimum": 0, + "maximum": 1 + }, + "question_text": { + "type": "string", + "minLength": 0, + "maxLength": 200 + } + } + } + } +} \ No newline at end of file diff --git a/Schema/CustomerAdminUpdateQuestionnairRequest.json b/Schema/CustomerAdminUpdateQuestionnairRequest.json new file mode 100644 index 0000000..5ef2b3f --- /dev/null +++ b/Schema/CustomerAdminUpdateQuestionnairRequest.json @@ -0,0 +1,54 @@ +{ + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "default": 1, + "minimum": 1, + "maximum": 99999999999 + }, + "title": { + "type": "string", + "minLength": 0, + "maxLength": 100 + }, + "status": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 999 + }, + "question": { + "type": "array", + "minItems": 0, + "maxItems": 200, + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 99999999999 + }, + "title": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "status": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1 + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Schema/CustomerAdminUpdateRelyTypeRequest.json b/Schema/CustomerAdminUpdateRelyTypeRequest.json new file mode 100644 index 0000000..719b1a8 --- /dev/null +++ b/Schema/CustomerAdminUpdateRelyTypeRequest.json @@ -0,0 +1,33 @@ +{ + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 9223372036854775807 + }, + "title": { + "type": "string", + "minLength": 0, + "maxLength": 20 + }, + "top": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 9999999999 + }, + "status": { + "type": "integer", + "default": 1, + "minimum": 0, + "maximum": 999 + } + } + } + } +} \ No newline at end of file diff --git a/Schema/CustomerAdminUpdateReplyRequest.json b/Schema/CustomerAdminUpdateReplyRequest.json new file mode 100644 index 0000000..a1b58c0 --- /dev/null +++ b/Schema/CustomerAdminUpdateReplyRequest.json @@ -0,0 +1,33 @@ +{ + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 9999999999 + }, + "content": { + "type": "string", + "minLength": 0, + "maxLength": 100 + }, + "top": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 9999999999 + }, + "type": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 9999999999 + } + } + } + } +} \ No newline at end of file diff --git a/Schema/DynamicAdminoffUpdateContentRequest.json b/Schema/DynamicAdminoffUpdateContentRequest.json new file mode 100644 index 0000000..e69de29 diff --git a/Schema/TestRequest.json b/Schema/TestRequest.json new file mode 100644 index 0000000..b52a196 --- /dev/null +++ b/Schema/TestRequest.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "account": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + } + } + } + } +} \ No newline at end of file diff --git a/Schema/UserTestRequest.json b/Schema/UserTestRequest.json new file mode 100644 index 0000000..b52a196 --- /dev/null +++ b/Schema/UserTestRequest.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "account": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + } + } + } + } +} \ No newline at end of file diff --git a/Schema/UserUserRequest.json b/Schema/UserUserRequest.json new file mode 100644 index 0000000..b52a196 --- /dev/null +++ b/Schema/UserUserRequest.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "account": { + "type": "string", + "format": "regex", + "pattern": "/^[a-z0-9]+$/i", + "minLength": 0, + "maxLength": 9223372036854775807 + } + } + } + } +} \ No newline at end of file diff --git a/build.example.php b/build.example.php new file mode 100644 index 0000000..ba9607c --- /dev/null +++ b/build.example.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- + +/** + * php think build 自动生成应用的目录结构的定义示例 + */ +return [ + // 需要自动创建的文件 + '__file__' => [], + // 需要自动创建的目录 + '__dir__' => ['controller', 'model', 'view'], + // 需要自动创建的控制器 + 'controller' => ['Index'], + // 需要自动创建的模型 + 'model' => ['AdminUser'], + // 需要自动创建的模板 + 'view' => ['index/index'], +]; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..2a15c66 --- /dev/null +++ b/composer.json @@ -0,0 +1,54 @@ +{ + "name": "topthink/think", + "description": "the new thinkphp framework", + "type": "project", + "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/framework": "6.0.*-dev", + "topthink/think-view": "^1.0", + "justinrainbow/json-schema": "~1.3", + "ramsey/uuid": "^3.8", + "predis/predis": "^1.1", + "topthink/think-multi-app": "^1.0", + "guzzlehttp/guzzle": "~6.0", + "topthink/think-socketlog": "^1.0", + "topthink/think-swoole": "^3.0" + }, + "autoload": { + "psr-4": { + "app\\": "app", + "longbingcore\\": "longbingcore" + }, + "psr-0": { + "": "extend/" + } + }, + "config": { + "preferred-install": "dist" + }, + "repositories": { + "packagist": { + "type": "composer", + "url": "https://packagist.phpcomposer.com" + } + }, + "scripts": { + "post-autoload-dump": [ + "@php think service:discover", + "@php think vendor:publish" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..9e0cd16 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1566 @@ +{ + "_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": "9f474e2a41296397a64a2793bdf81e78", + "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "6.5.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "0274c05370a7bc9bb3a33838858253418bd7d14b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0274c05370a7bc9bb3a33838858253418bd7d14b", + "reference": "0274c05370a7bc9bb3a33838858253418bd7d14b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2019-12-21T08:51:15+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", + "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.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "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" + ], + "time": "2019-07-01T23:21:34+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/cc84765fb7317f6b07bd8ac78364747f95b86341", + "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341", + "shasum": "" + }, + "require": { + "php": ">=5.3.29" + }, + "require-dev": { + "json-schema/json-schema-test-suite": "1.1.0", + "phpdocumentor/phpdocumentor": "~2", + "phpunit/phpunit": "~3.7" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "time": "2016-01-25T15:43:01+00:00" + }, + { + "name": "league/flysystem", + "version": "1.0.61", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "4fb13c01784a6c9f165a351e996871488ca2d8c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4fb13c01784a6c9f165a351e996871488ca2d8c9", + "reference": "4fb13c01784a6c9f165a351e996871488ca2d8c9", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7.10" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "time": "2019-12-08T21:46:50+00:00" + }, + { + "name": "league/flysystem-cached-adapter", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-cached-adapter.git", + "reference": "08ef74e9be88100807a3b92cc9048a312bf01d6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-cached-adapter/zipball/08ef74e9be88100807a3b92cc9048a312bf01d6f", + "reference": "08ef74e9be88100807a3b92cc9048a312bf01d6f", + "shasum": "" + }, + "require": { + "league/flysystem": "~1.0", + "psr/cache": "^1.0.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0", + "tedivm/stash": "~0.12" + }, + "suggest": { + "ext-phpredis": "Pure C implemented extension for PHP" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Cached\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "frankdejonge", + "email": "info@frenky.net" + } + ], + "description": "An adapter decorator to enable meta-data caching.", + "time": "2018-07-09T20:51:04+00:00" + }, + { + "name": "nette/php-generator", + "version": "v3.3.1", + "source": { + "type": "git", + "url": "https://github.com/nette/php-generator.git", + "reference": "4240fd7adf499138c07b814ef9b9a6df9f6d7187" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/php-generator/zipball/4240fd7adf499138c07b814ef9b9a6df9f6d7187", + "reference": "4240fd7adf499138c07b814ef9b9a6df9f6d7187", + "shasum": "" + }, + "require": { + "nette/utils": "^2.4.2 || ~3.0.0", + "php": ">=7.1" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.3 features.", + "homepage": "https://nette.org", + "keywords": [ + "code", + "nette", + "php", + "scaffolding" + ], + "time": "2019-11-22T11:12:11+00:00" + }, + { + "name": "nette/utils", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "c133e18c922dcf3ad07673077d92d92cef25a148" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/c133e18c922dcf3ad07673077d92d92cef25a148", + "reference": "c133e18c922dcf3ad07673077d92d92cef25a148", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "nette/tester": "~2.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize() and toAscii()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "time": "2019-10-21T20:40:16+00:00" + }, + { + "name": "opis/closure", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/93ebc5712cdad8d5f489b500c59d122df2e53969", + "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\Closure\\": "src/" + }, + "files": [ + "functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "time": "2019-11-29T22:36:02+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": "predis/predis", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/nrk/predis.git", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net" + } + ], + "description": "Flexible and feature-complete Redis client for PHP and HHVM", + "homepage": "http://github.com/nrk/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "time": "2016-06-16T16:22:20+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+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/http-message", + "version": "1.0.1", + "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": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "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" + ], + "time": "2016-08-06T14:39:51+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": "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" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/uuid", + "version": "3.9.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "7779489a47d443f845271badbdcedfe4df8e06fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/7779489a47d443f845271badbdcedfe4df8e06fb", + "reference": "7779489a47d443f845271badbdcedfe4df8e06fb", + "shasum": "" + }, + "require": { + "ext-json": "*", + "paragonie/random_compat": "^1 | ^2 | 9.99.99", + "php": "^5.4 | ^7 | ^8", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^1 | ^2", + "doctrine/annotations": "^1.2", + "goaop/framework": "1.0.0-alpha.2 | ^1 | ^2.1", + "jakub-onderka/php-parallel-lint": "^1", + "mockery/mockery": "^0.9.11 | ^1", + "moontoast/math": "^1.1", + "paragonie/random-lib": "^2", + "php-mock/php-mock-phpunit": "^0.3 | ^1.1", + "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "ext-ctype": "Provides support for PHP Ctype functions", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + }, + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "time": "2019-12-17T08:18:51+00:00" + }, + { + "name": "swoole/ide-helper", + "version": "4.4.12", + "source": { + "type": "git", + "url": "https://github.com/swoole/ide-helper.git", + "reference": "915964abd1ff4fe78be3e341ce9008b6d6a7648a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swoole/ide-helper/zipball/915964abd1ff4fe78be3e341ce9008b6d6a7648a", + "reference": "915964abd1ff4fe78be3e341ce9008b6d6a7648a", + "shasum": "" + }, + "require-dev": { + "squizlabs/php_codesniffer": "~3.4.0", + "symfony/filesystem": "~4.3.0", + "zendframework/zend-code": "~3.3.0" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Team Swoole", + "email": "team@swoole.com" + } + ], + "description": "IDE help files for Swoole.", + "time": "2019-11-04T17:53:06+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "ce8743441da64c41e2a667b8eb66070444ed911e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/ce8743441da64c41e2a667b8eb66070444ed911e", + "reference": "ce8743441da64c41e2a667b8eb66070444ed911e", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-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-11-17T21:56:56+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.13.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-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-11-27T13:56:44+00:00" + }, + { + "name": "topthink/framework", + "version": "6.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/top-think/framework.git", + "reference": "2e57ce8ccfddadf9b31fb41ac96a8cf15ee034f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/framework/zipball/2e57ce8ccfddadf9b31fb41ac96a8cf15ee034f0", + "reference": "2e57ce8ccfddadf9b31fb41ac96a8cf15ee034f0", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "league/flysystem": "^1.0", + "league/flysystem-cached-adapter": "^1.0", + "opis/closure": "^3.1", + "php": ">=7.1.0", + "psr/container": "~1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-helper": "^3.1.1", + "topthink/think-orm": "^2.0" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "autoload": { + "files": [], + "psr-4": { + "think\\": "src/think/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP Framework.", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "framework", + "orm", + "thinkphp" + ], + "time": "2019-12-22T12:44:23+00:00" + }, + { + "name": "topthink/think-helper", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "4d85dfd3778623bbb1de3648f1dcd0c82f4439f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/4d85dfd3778623bbb1de3648f1dcd0c82f4439f4", + "reference": "4d85dfd3778623bbb1de3648f1dcd0c82f4439f4", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "src/helper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6 Helper Package", + "time": "2019-09-30T02:36:48+00:00" + }, + { + "name": "topthink/think-multi-app", + "version": "v1.0.11", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-multi-app.git", + "reference": "215f4a6bb88e53ad41b448c61957336eb55ce6f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-multi-app/zipball/215f4a6bb88e53ad41b448c61957336eb55ce6f9", + "reference": "215f4a6bb88e53ad41b448c61957336eb55ce6f9", + "shasum": "" + }, + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0.0" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\app\\Service" + ] + } + }, + "autoload": { + "psr-4": { + "think\\app\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp6 multi app support", + "time": "2019-10-29T06:34:59+00:00" + }, + { + "name": "topthink/think-orm", + "version": "v2.0.28", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-orm.git", + "reference": "ff5f112f5559497222f2716385b5a709ab82741b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-orm/zipball/ff5f112f5559497222f2716385b5a709ab82741b", + "reference": "ff5f112f5559497222f2716385b5a709ab82741b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-helper": "^3.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "think orm", + "keywords": [ + "database", + "orm" + ], + "time": "2019-11-17T07:53:45+00:00" + }, + { + "name": "topthink/think-socketlog", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-socketlog.git", + "reference": "fe7134c41026dc74176796d4a6b7f57ae3fa1a84" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-socketlog/zipball/fe7134c41026dc74176796d4a6b7f57ae3fa1a84", + "reference": "fe7134c41026dc74176796d4a6b7f57ae3fa1a84", + "shasum": "" + }, + "require": { + "topthink/framework": "^6.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\log\\driver\\": "src" + }, + "files": [] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "SocketLog for thinkphp", + "time": "2019-06-10T08:15:30+00:00" + }, + { + "name": "topthink/think-swoole", + "version": "v3.0.5", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-swoole.git", + "reference": "e36eff2f2bd88b5fe30631f55aa6b1c27b89aefa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-swoole/zipball/e36eff2f2bd88b5fe30631f55aa6b1c27b89aefa", + "reference": "e36eff2f2bd88b5fe30631f55aa6b1c27b89aefa", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-swoole": ">=4.0.0", + "nette/php-generator": "^3.2", + "swoole/ide-helper": "^4.3", + "symfony/finder": "^4.3.2", + "topthink/framework": "^6.0", + "topthink/think-helper": "^3.0.4" + }, + "require-dev": { + "symfony/var-dumper": "^4.3" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\swoole\\Service" + ], + "config": { + "swoole": "src/config/swoole.php" + } + } + }, + "autoload": { + "psr-4": { + "think\\swoole\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "Swoole extend for thinkphp", + "time": "2019-10-21T12:03:07+00:00" + }, + { + "name": "topthink/think-template", + "version": "v2.0.7", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-template.git", + "reference": "e98bdbb4a4c94b442f17dfceba81e0134d4fbd19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-template/zipball/e98bdbb4a4c94b442f17dfceba81e0134d4fbd19", + "reference": "e98bdbb4a4c94b442f17dfceba81e0134d4fbd19", + "shasum": "" + }, + "require": { + "php": ">=7.1.0", + "psr/simple-cache": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the php template engine", + "time": "2019-09-20T15:31:04+00:00" + }, + { + "name": "topthink/think-view", + "version": "v1.0.13", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-view.git", + "reference": "90803b73f781db5d42619082c4597afc58b2d4c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-view/zipball/90803b73f781db5d42619082c4597afc58b2d4c5", + "reference": "90803b73f781db5d42619082c4597afc58b2d4c5", + "shasum": "" + }, + "require": { + "php": ">=7.1.0", + "topthink/think-template": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\view\\driver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp template driver", + "time": "2019-10-07T12:23:10+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "topthink/framework": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.1.0" + }, + "platform-dev": [] +} diff --git a/route/app.php b/route/app.php new file mode 100644 index 0000000..a63d542 --- /dev/null +++ b/route/app.php @@ -0,0 +1,14 @@ + +// +---------------------------------------------------------------------- +use think\facade\Route; + +Route::any('auth', '\app\auth\Index@index'); +Route::post('user' ,'app\controller\User@Test'); \ No newline at end of file diff --git a/think b/think new file mode 100644 index 0000000..b7e9aa4 --- /dev/null +++ b/think @@ -0,0 +1,19 @@ +#!/usr/bin/env php + +// +---------------------------------------------------------------------- + +namespace think; + +// 加载基础文件 +require __DIR__ . '/vendor/autoload.php'; + +// 应用初始化 +(new App())->console->run(); \ No newline at end of file diff --git a/vendor/adbario/php-dot-notation/LICENSE.md b/vendor/adbario/php-dot-notation/LICENSE.md new file mode 100644 index 0000000..fe01323 --- /dev/null +++ b/vendor/adbario/php-dot-notation/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) 2016-2019 Riku Särkinen + +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/adbario/php-dot-notation/composer.json b/vendor/adbario/php-dot-notation/composer.json new file mode 100644 index 0000000..b7b82cb --- /dev/null +++ b/vendor/adbario/php-dot-notation/composer.json @@ -0,0 +1,29 @@ +{ + "name": "adbario/php-dot-notation", + "description": "PHP dot notation access to arrays", + "keywords": ["dotnotation", "arrayaccess"], + "homepage": "https://github.com/adbario/php-dot-notation", + "license": "MIT", + "authors": [ + { + "name": "Riku Särkinen", + "email": "riku@adbar.io" + } + ], + "require": { + "php": ">=5.5", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "^4.0|^5.0|^6.0", + "squizlabs/php_codesniffer": "^3.0" + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Adbar\\": "src" + } + } +} diff --git a/vendor/adbario/php-dot-notation/src/Dot.php b/vendor/adbario/php-dot-notation/src/Dot.php new file mode 100644 index 0000000..8d504d9 --- /dev/null +++ b/vendor/adbario/php-dot-notation/src/Dot.php @@ -0,0 +1,601 @@ + + * @link https://github.com/adbario/php-dot-notation + * @license https://github.com/adbario/php-dot-notation/blob/2.x/LICENSE.md (MIT License) + */ +namespace Adbar; + +use Countable; +use ArrayAccess; +use ArrayIterator; +use JsonSerializable; +use IteratorAggregate; + +/** + * Dot + * + * This class provides a dot notation access and helper functions for + * working with arrays of data. Inspired by Laravel Collection. + */ +class Dot implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * The stored items + * + * @var array + */ + protected $items = []; + + /** + * Create a new Dot instance + * + * @param mixed $items + */ + public function __construct($items = []) + { + $this->items = $this->getArrayItems($items); + } + + /** + * Set a given key / value pair or pairs + * if the key doesn't exist already + * + * @param array|int|string $keys + * @param mixed $value + */ + public function add($keys, $value = null) + { + if (is_array($keys)) { + foreach ($keys as $key => $value) { + $this->add($key, $value); + } + } elseif (is_null($this->get($keys))) { + $this->set($keys, $value); + } + } + + /** + * Return all the stored items + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Delete the contents of a given key or keys + * + * @param array|int|string|null $keys + */ + public function clear($keys = null) + { + if (is_null($keys)) { + $this->items = []; + + return; + } + + $keys = (array) $keys; + + foreach ($keys as $key) { + $this->set($key, []); + } + } + + /** + * Delete the given key or keys + * + * @param array|int|string $keys + */ + public function delete($keys) + { + $keys = (array) $keys; + + foreach ($keys as $key) { + if ($this->exists($this->items, $key)) { + unset($this->items[$key]); + + continue; + } + + $items = &$this->items; + $segments = explode('.', $key); + $lastSegment = array_pop($segments); + + foreach ($segments as $segment) { + if (!isset($items[$segment]) || !is_array($items[$segment])) { + continue 2; + } + + $items = &$items[$segment]; + } + + unset($items[$lastSegment]); + } + } + + /** + * Checks if the given key exists in the provided array. + * + * @param array $array Array to validate + * @param int|string $key The key to look for + * + * @return bool + */ + protected function exists($array, $key) + { + return array_key_exists($key, $array); + } + + /** + * Flatten an array with the given character as a key delimiter + * + * @param string $delimiter + * @param array|null $items + * @param string $prepend + * @return array + */ + public function flatten($delimiter = '.', $items = null, $prepend = '') + { + $flatten = []; + + if (is_null($items)) { + $items = $this->items; + } + + foreach ($items as $key => $value) { + if (is_array($value) && !empty($value)) { + $flatten = array_merge( + $flatten, + $this->flatten($delimiter, $value, $prepend.$key.$delimiter) + ); + } else { + $flatten[$prepend.$key] = $value; + } + } + + return $flatten; + } + + /** + * Return the value of a given key + * + * @param int|string|null $key + * @param mixed $default + * @return mixed + */ + public function get($key = null, $default = null) + { + if (is_null($key)) { + return $this->items; + } + + if ($this->exists($this->items, $key)) { + return $this->items[$key]; + } + + if (strpos($key, '.') === false) { + return $default; + } + + $items = $this->items; + + foreach (explode('.', $key) as $segment) { + if (!is_array($items) || !$this->exists($items, $segment)) { + return $default; + } + + $items = &$items[$segment]; + } + + return $items; + } + + /** + * Return the given items as an array + * + * @param mixed $items + * @return array + */ + protected function getArrayItems($items) + { + if (is_array($items)) { + return $items; + } elseif ($items instanceof self) { + return $items->all(); + } + + return (array) $items; + } + + /** + * Check if a given key or keys exists + * + * @param array|int|string $keys + * @return bool + */ + public function has($keys) + { + $keys = (array) $keys; + + if (!$this->items || $keys === []) { + return false; + } + + foreach ($keys as $key) { + $items = $this->items; + + if ($this->exists($items, $key)) { + continue; + } + + foreach (explode('.', $key) as $segment) { + if (!is_array($items) || !$this->exists($items, $segment)) { + return false; + } + + $items = $items[$segment]; + } + } + + return true; + } + + /** + * Check if a given key or keys are empty + * + * @param array|int|string|null $keys + * @return bool + */ + public function isEmpty($keys = null) + { + if (is_null($keys)) { + return empty($this->items); + } + + $keys = (array) $keys; + + foreach ($keys as $key) { + if (!empty($this->get($key))) { + return false; + } + } + + return true; + } + + /** + * Merge a given array or a Dot object with the given key + * or with the whole Dot object + * + * @param array|string|self $key + * @param array|self $value + */ + public function merge($key, $value = []) + { + if (is_array($key)) { + $this->items = array_merge($this->items, $key); + } elseif (is_string($key)) { + $items = (array) $this->get($key); + $value = array_merge($items, $this->getArrayItems($value)); + + $this->set($key, $value); + } elseif ($key instanceof self) { + $this->items = array_merge($this->items, $key->all()); + } + } + + /** + * Recursively merge a given array or a Dot object with the given key + * or with the whole Dot object. + * + * Duplicate keys are converted to arrays. + * + * @param array|string|self $key + * @param array|self $value + */ + public function mergeRecursive($key, $value = []) + { + if (is_array($key)) { + $this->items = array_merge_recursive($this->items, $key); + } elseif (is_string($key)) { + $items = (array) $this->get($key); + $value = array_merge_recursive($items, $this->getArrayItems($value)); + + $this->set($key, $value); + } elseif ($key instanceof self) { + $this->items = array_merge_recursive($this->items, $key->all()); + } + } + + /** + * Recursively merge a given array or a Dot object with the given key + * or with the whole Dot object. + * + * Instead of converting duplicate keys to arrays, the value from + * given array will replace the value in Dot object. + * + * @param array|string|self $key + * @param array|self $value + */ + public function mergeRecursiveDistinct($key, $value = []) + { + if (is_array($key)) { + $this->items = $this->arrayMergeRecursiveDistinct($this->items, $key); + } elseif (is_string($key)) { + $items = (array) $this->get($key); + $value = $this->arrayMergeRecursiveDistinct($items, $this->getArrayItems($value)); + + $this->set($key, $value); + } elseif ($key instanceof self) { + $this->items = $this->arrayMergeRecursiveDistinct($this->items, $key->all()); + } + } + + /** + * Merges two arrays recursively. In contrast to array_merge_recursive, + * duplicate keys are not converted to arrays but rather overwrite the + * value in the first array with the duplicate value in the second array. + * + * @param array $array1 Initial array to merge + * @param array $array2 Array to recursively merge + * @return array + */ + protected function arrayMergeRecursiveDistinct(array $array1, array $array2) + { + $merged = &$array1; + + foreach ($array2 as $key => $value) { + if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) { + $merged[$key] = $this->arrayMergeRecursiveDistinct($merged[$key], $value); + } else { + $merged[$key] = $value; + } + } + + return $merged; + } + + /** + * Return the value of a given key and + * delete the key + * + * @param int|string|null $key + * @param mixed $default + * @return mixed + */ + public function pull($key = null, $default = null) + { + if (is_null($key)) { + $value = $this->all(); + $this->clear(); + + return $value; + } + + $value = $this->get($key, $default); + $this->delete($key); + + return $value; + } + + /** + * Push a given value to the end of the array + * in a given key + * + * @param mixed $key + * @param mixed $value + */ + public function push($key, $value = null) + { + if (is_null($value)) { + $this->items[] = $key; + + return; + } + + $items = $this->get($key); + + if (is_array($items) || is_null($items)) { + $items[] = $value; + $this->set($key, $items); + } + } + + /** + * Replace all values or values within the given key + * with an array or Dot object + * + * @param array|string|self $key + * @param array|self $value + */ + public function replace($key, $value = []) + { + if (is_array($key)) { + $this->items = array_replace($this->items, $key); + } elseif (is_string($key)) { + $items = (array) $this->get($key); + $value = array_replace($items, $this->getArrayItems($value)); + + $this->set($key, $value); + } elseif ($key instanceof self) { + $this->items = array_replace($this->items, $key->all()); + } + } + + /** + * Set a given key / value pair or pairs + * + * @param array|int|string $keys + * @param mixed $value + */ + public function set($keys, $value = null) + { + if (is_array($keys)) { + foreach ($keys as $key => $value) { + $this->set($key, $value); + } + + return; + } + + $items = &$this->items; + + foreach (explode('.', $keys) as $key) { + if (!isset($items[$key]) || !is_array($items[$key])) { + $items[$key] = []; + } + + $items = &$items[$key]; + } + + $items = $value; + } + + /** + * Replace all items with a given array + * + * @param mixed $items + */ + public function setArray($items) + { + $this->items = $this->getArrayItems($items); + } + + /** + * Replace all items with a given array as a reference + * + * @param array $items + */ + public function setReference(array &$items) + { + $this->items = &$items; + } + + /** + * Return the value of a given key or all the values as JSON + * + * @param mixed $key + * @param int $options + * @return string + */ + public function toJson($key = null, $options = 0) + { + if (is_string($key)) { + return json_encode($this->get($key), $options); + } + + $options = $key === null ? 0 : $key; + + return json_encode($this->items, $options); + } + + /* + * -------------------------------------------------------------- + * ArrayAccess interface + * -------------------------------------------------------------- + */ + + /** + * Check if a given key exists + * + * @param int|string $key + * @return bool + */ + public function offsetExists($key) + { + return $this->has($key); + } + + /** + * Return the value of a given key + * + * @param int|string $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Set a given value to the given key + * + * @param int|string|null $key + * @param mixed $value + */ + public function offsetSet($key, $value) + { + if (is_null($key)) { + $this->items[] = $value; + + return; + } + + $this->set($key, $value); + } + + /** + * Delete the given key + * + * @param int|string $key + */ + public function offsetUnset($key) + { + $this->delete($key); + } + + /* + * -------------------------------------------------------------- + * Countable interface + * -------------------------------------------------------------- + */ + + /** + * Return the number of items in a given key + * + * @param int|string|null $key + * @return int + */ + public function count($key = null) + { + return count($this->get($key)); + } + + /* + * -------------------------------------------------------------- + * IteratorAggregate interface + * -------------------------------------------------------------- + */ + + /** + * Get an iterator for the stored items + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /* + * -------------------------------------------------------------- + * JsonSerializable interface + * -------------------------------------------------------------- + */ + + /** + * Return items for JSON serialization + * + * @return array + */ + public function jsonSerialize() + { + return $this->items; + } +} diff --git a/vendor/adbario/php-dot-notation/src/helpers.php b/vendor/adbario/php-dot-notation/src/helpers.php new file mode 100644 index 0000000..ffdc826 --- /dev/null +++ b/vendor/adbario/php-dot-notation/src/helpers.php @@ -0,0 +1,23 @@ + + * @link https://github.com/adbario/php-dot-notation + * @license https://github.com/adbario/php-dot-notation/blob/2.x/LICENSE.md (MIT License) + */ + +use Adbar\Dot; + +if (! function_exists('dot')) { + /** + * Create a new Dot object with the given items + * + * @param mixed $items + * @return \Adbar\Dot + */ + function dot($items) + { + return new Dot($items); + } +} diff --git a/vendor/alibabacloud/client/CHANGELOG.md b/vendor/alibabacloud/client/CHANGELOG.md new file mode 100644 index 0000000..7111e73 --- /dev/null +++ b/vendor/alibabacloud/client/CHANGELOG.md @@ -0,0 +1,275 @@ +# CHANGELOG + +## 1.5.21 - 2020-02-26 +- Improved Nonce. +- Updated Endpoints. + +## 1.5.20 - 2019-12-30 +- Improved Docs. +- Updated Endpoints. + +## 1.5.19 - 2019-12-17 +- Updated Endpoints. + +## 1.5.18 - 2019-10-11 +- Updated Request link. +- Updated Endpoints data. + +## 1.5.17 - 2019-09-15 +- Improved Host Finder. +- Updated Endpoints Data. + +## 1.5.16 - 2019-08-21 +- Updated Endpoints Data. + +## 1.5.15 - 2019-08-14 +- Improved Client. + + +## 1.5.14 - 2019-07-25 +- Improved Credential Filter. + + +## 1.5.13 - 2019-07-18 +- Improved API Resolver. + + +## 1.5.12 - 2019-06-20 +- Fixed Signature for ROA. + + +## 1.5.11 - 2019-06-14 +- Added endpoint rules. + + +## 1.5.10 - 2019-06-13 +- Improved `Resovler`. +- Updated `endpoints`. + + +## 1.5.9 - 2019-06-04 +- Improved `UUID`. + + +## 1.5.8 - 2019-05-30 +- Improved `Arrays`. + + +## 1.5.7 - 2019-05-29 +- Improved `uuid`. + + +## 1.5.6 - 2019-05-29 +- Fixed `uuid` version lock. + + +## 1.5.5 - 2019-05-23 +- Improved `Signature`. + + +## 1.5.4 - 2019-05-22 +- Updated `Endpoints`. +- Fixed `Content-Type` in header. + + +## 1.5.3 - 2019-05-13 +- Improved `Endpoint` tips. +- Improved `Endpoints` for `STS`. + + +## 1.5.2 - 2019-05-10 +- Improved `Result` object. + + +## 1.5.1 - 2019-05-09 +- Supported `Resolver` for Third-party dependencies. + + +## 1.5.0 - 2019-05-07 +- Improved `Resolver` for products. + + +## 1.4.0 - 2019-05-06 +- Support `Retry` and `Asynchronous` for Request. + + +## 1.3.1 - 2019-04-30 +- Allow timeouts to be set in microseconds. + + +## 1.3.0 - 2019-04-18 +- Improved parameters methods. +- Optimized the logic for body encode. + + +## 1.2.1 - 2019-04-11 +- Improve exception code and message for `Region ID`. + + +## 1.2.0 - 2019-04-11 +- Improve exception message for `Region ID`. + + +## 1.1.1 - 2019-04-02 +- Added endpoints for `batchcomputenew`, `privatelink`. +- Improve Region ID tips. + + +## 1.1.0 - 2019-04-01 +- Updated `composer.json`. + + +## 1.0.27 - 2019-03-31 +- Support `Policy` for `ramRoleArnClient`. + + +## 1.0.26 - 2019-03-27 +- Support `pid`, `cost`, `start_time` for Log. + + +## 1.0.25 - 2019-03-27 +- Updated default log format. +- Add endpoints for `dbs`. + + +## 1.0.24 - 2019-03-26 +- Support Log. + + +## 1.0.23 - 2019-03-23 +- Remove SVG. + + +## 1.0.22 - 2019-03-20 +- Add endpoint `cn-hangzhou` for `idaas` . + + +## 1.0.21 - 2019-03-19 +- Installing by Using the ZIP file. +- Update Docs. + + +## 1.0.20 - 2019-03-13 +- Improve Tests. +- Update Docs. + + +## 1.0.19 - 2019-03-12 +- Add SSL Verify Option `verify()`. + + +## 1.0.18 - 2019-03-11 +- Add endpoints for `acr`. +- Add endpoints for `faas`. +- Add endpoints for `ehs`. +- SSL certificates are not validated by default. + + +## 1.0.17 - 2019-03-08 +- Support Mock for Test. + + +## 1.0.16 - 2019-03-07 +- Support Credential Provider Chain. +- Support `CCC`. +- Add `ap-south-1` for `cas`. +- Add `ap-southeast-1` for `waf`. +- Update Docs. + + +## 1.0.15 - 2019-02-27 +- Add endpoints for `Chatbot`. +- Change endpoints for `drdspost` and `drdspre`. + + +## 1.0.14 - 2019-02-21 +- Enable debug mode by set environment variable `DEBUG=sdk`. + + +## 1.0.13 - 2019-02-18 +- Support Release Script `composer release`. +- Add endpoints for apigateway in `drdspre` in `cn-qingdao`. +- Add endpoints for apigateway in `drdspre` in `cn-beijing`. +- Add endpoints for apigateway in `drdspre` in `cn-hangzhou`. +- Add endpoints for apigateway in `drdspre` in `cn-shanghai`. +- Add endpoints for apigateway in `drdspre` in `cn-shenzhen`. +- Add endpoints for apigateway in `drdspre` in `cn-hongkong`. +- Add endpoints for apigateway in `drdspost` in `ap-southeast-1`. +- Add endpoints for apigateway in `drdspost` in `cn-shanghai`. +- Add endpoints for apigateway in `drdspost` in `cn-hongkong`. +- Add endpoints for apigateway in `vod` in `ap-southeast-1`. +- Add endpoints for apigateway in `vod` in `eu-central-1`. + + +## 1.0.12 - 2019-02-16 +- Support `open_basedir`. + + +## 1.0.11 - 2019-02-13 +- Improve User Agent. + + +## 1.0.10 - 2019-02-12 +- `userAgentAppend` is renamed to `appendUserAgent`. + + +## 1.0.9 - 2019-02-12 +- `userAgent` is renamed to `userAgentAppend`. + + +## 1.0.8 - 2019-02-11 +- `userAgent` - Support DIY User Agent. +- Add endpoints for apigateway in Zhangjiakou. +- Add endpoints for apigateway in Hu He Hao Te. +- Add endpoints for vod in Hu He Hao Te. +- Add endpoints for hsm in Zhangjiakou. +- Add endpoints for luban in Germany. +- Add endpoints for linkwan in Hangzhou. +- Add endpoints for drdspost in Singapore. + + +## 1.0.7 - 2019-01-28 +- Add endpoints for gpdb in Tokyo. +- Add endpoints for elasticsearch in Beijing. + + +## 1.0.6 - 2019-01-23 +- Add endpoints for dysmsapi in Singapore. +- Add endpoints for dybaseapi. +- Add endpoints for dyiotapi. +- Add endpoints for dycdpapi. +- Add endpoints for dyplsapi. +- Add endpoints for dypnsapi. +- Add endpoints for dyvmsapi. +- Add endpoints for snsuapi. + + +## 1.0.5 - 2019-01-21 +- Add endpoints for ApiGateway in Silicon Valley, Virginia. +- Add endpoints for Image Search in Shanghai. + + +## 1.0.4 - 2019-01-17 +- Support fixer all. +- Add Endpoints. + + +## 1.0.3 - 2019-01-15 +- Update Endpoints. +- Update README.md. +- Update Return Result Message. + + +## 1.0.2 - 2019-01-15 +- Optimize the documentation. +- Adjust the CI configuration. + + +## 1.0.1 - 2019-01-09 +- Distinguish credential error. +- Add endpoints for NLS. +- Add not found product tip. + + +## 1.0.0 - 2019-01-07 +- Initial release of the Alibaba Cloud Client for PHP Version 1.0.0 on Packagist See for more information. diff --git a/vendor/alibabacloud/client/CONTRIBUTING.md b/vendor/alibabacloud/client/CONTRIBUTING.md new file mode 100644 index 0000000..a1c52a0 --- /dev/null +++ b/vendor/alibabacloud/client/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# Contributing to the Alibaba Cloud Client for PHP + +We work hard to provide a high-quality and useful SDK for Alibaba Cloud, and +we greatly value feedback and contributions from our community. Please submit +your [issues][issues] or [pull requests][pull-requests] through GitHub. + +## Tips + +- The SDK is released under the [Apache license][license]. Any code you submit + will be released under that license. For substantial contributions, we may + ask you to sign a [Alibaba Documentation Corporate Contributor License + Agreement (CLA)][cla]. +- We follow all of the relevant PSR recommendations from the [PHP Framework + Interop Group][php-fig]. Please submit code that follows these standards. + The [PHP CS Fixer][cs-fixer] tool can be helpful for formatting your code. + Your can use `composer fixer` to fix code. +- We maintain a high percentage of code coverage in our unit tests. If you make + changes to the code, please add, update, and/or remove tests as appropriate. +- If your code does not conform to the PSR standards, does not include adequate + tests, or does not contain a changelog document, we may ask you to update + your pull requests before we accept them. We also reserve the right to deny + any pull requests that do not align with our standards or goals. + +[issues]: https://github.com/aliyun/openapi-sdk-php-client/issues +[pull-requests]: https://github.com/aliyun/openapi-sdk-php-client/pulls +[license]: http://www.apache.org/licenses/LICENSE-2.0 +[cla]: https://alibaba-cla-2018.oss-cn-beijing.aliyuncs.com/Alibaba_Documentation_Open_Source_Corporate_CLA.pdf +[php-fig]: http://php-fig.org +[cs-fixer]: http://cs.sensiolabs.org/ +[docs-readme]: https://github.com/aliyun/openapi-sdk-php-client/blob/master/README.md diff --git a/vendor/alibabacloud/client/LICENSE.md b/vendor/alibabacloud/client/LICENSE.md new file mode 100644 index 0000000..ec13fcc --- /dev/null +++ b/vendor/alibabacloud/client/LICENSE.md @@ -0,0 +1,13 @@ +Copyright (c) 2009-present, Alibaba Cloud All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/alibabacloud/client/NOTICE.md b/vendor/alibabacloud/client/NOTICE.md new file mode 100644 index 0000000..db04164 --- /dev/null +++ b/vendor/alibabacloud/client/NOTICE.md @@ -0,0 +1,88 @@ +# Alibaba Cloud Client for PHP + + + +Copyright (c) 2009-present, Alibaba Cloud All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"). +You may not use this file except in compliance with the License. +A copy of the License is located at + + + +or in the "license" file accompanying this file. This file is distributed +on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +express or implied. See the License for the specific language governing +permissions and limitations under the License. + +# Guzzle + + + +Copyright (c) 2011-2018 Michael Dowling, https://github.com/mtdowling + +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. + +# jmespath.php + + + +Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling + +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. + +# Dot + + + +Copyright (c) 2016-2019 Riku Särkinen + +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/alibabacloud/client/README-zh-CN.md b/vendor/alibabacloud/client/README-zh-CN.md new file mode 100644 index 0000000..5814a6e --- /dev/null +++ b/vendor/alibabacloud/client/README-zh-CN.md @@ -0,0 +1,161 @@ +[English](/README.md) | 简体中文 + + +# Alibaba Cloud Client for PHP +[![Latest Stable Version](https://poser.pugx.org/alibabacloud/client/v/stable)](https://packagist.org/packages/alibabacloud/client) +[![composer.lock](https://poser.pugx.org/alibabacloud/client/composerlock)](https://packagist.org/packages/alibabacloud/client) +[![Total Downloads](https://poser.pugx.org/alibabacloud/client/downloads)](https://packagist.org/packages/alibabacloud/client) +[![License](https://poser.pugx.org/alibabacloud/client/license)](https://packagist.org/packages/alibabacloud/client) +[![codecov](https://codecov.io/gh/aliyun/openapi-sdk-php-client/branch/master/graph/badge.svg)](https://codecov.io/gh/aliyun/openapi-sdk-php-client) +[![Travis Build Status](https://travis-ci.org/aliyun/openapi-sdk-php-client.svg?branch=master)](https://travis-ci.org/aliyun/openapi-sdk-php-client) +[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/qb8j3lhg8349k0hk/branch/master?svg=true)](https://ci.appveyor.com/project/aliyun/openapi-sdk-php-client/branch/master) + + +![](https://aliyunsdk-pages.alicdn.com/icons/AlibabaCloud.svg) + + +Alibaba Cloud Client for PHP 是帮助 PHP 开发者管理凭据、发送请求的客户端工具,[Alibaba Cloud SDK for PHP][SDK] 由本工具提供底层支持。 + + +## 在线示例 +[API Explorer](https://api.aliyun.com) 提供在线调用阿里云产品,并动态生成 SDK 代码和快速检索接口等能力,能显著降低使用云 API 的难度。 + + +## 先决条件 +您的系统需要满足[先决条件](/docs/zh-CN/0-Prerequisites.md),包括 PHP> = 5.5。 我们强烈建议使用cURL扩展,并使用TLS后端编译cURL 7.16.2+。 + + +## 安装依赖 +如果已在系统上[全局安装 Composer](https://getcomposer.org/doc/00-intro.md#globally),请直接在项目目录中运行以下内容来安装 Alibaba Cloud Client for PHP 作为依赖项: +``` +composer require alibabacloud/client +``` +> 一些用户可能由于网络问题无法安装,可以使用[阿里云 Composer 全量镜像](https://developer.aliyun.com/composer)。 + +请看[安装](/docs/zh-CN/1-Installation.md)有关通过 Composer 和其他方式安装的详细信息。 + + +## 快速使用 +在您开始之前,您需要注册阿里云帐户并获取您的[凭证](https://usercenter.console.aliyun.com/#/manage/ak)。 + +```php +asDefaultClient(); +``` + +## ROA 请求 +```php +regionId('cn-hangzhou') // 指定请求的区域,不指定则使用客户端区域、默认区域 + ->product('CS') // 指定产品 + ->version('2015-12-15') // 指定产品版本 + ->action('DescribeClusterServices') // 指定产品接口 + ->serviceCode('cs') // 设置 ServiceCode 以备寻址,非必须 + ->endpointType('openAPI') // 设置类型,非必须 + ->method('GET') // 指定请求方式 + ->host('cs.aliyun.com') // 指定域名则不会寻址,如认证方式为 Bearer Token 的服务则需要指定 + ->pathPattern('/clusters/[ClusterId]/services') // 指定ROA风格路径规则 + ->withClusterId('123456') // 为路径中参数赋值,方法名:with + 参数 + ->request(); // 发起请求并返回结果对象,请求需要放在设置的最后面 + + print_r($result->toArray()); + +} catch (ClientException $exception) { + print_r($exception->getErrorMessage()); +} catch (ServerException $exception) { + print_r($exception->getErrorMessage()); +} +``` + +## RPC 请求 +```php +product('Cdn') + ->version('2014-11-11') + ->action('DescribeCdnService') + ->method('POST') + ->request(); + + print_r($result->toArray()); + +} catch (ClientException $exception) { + print_r($exception->getErrorMessage()); +} catch (ServerException $exception) { + print_r($exception->getErrorMessage()); +} +``` + + +## 文档 +* [先决条件](/docs/zh-CN/0-Prerequisites.md) +* [安装](/docs/zh-CN/1-Installation.md) +* [客户端](/docs/zh-CN/2-Client.md) +* [请求](/docs/zh-CN/3-Request.md) +* [结果](/docs/zh-CN/4-Result.md) +* [区域](/docs/zh-CN/5-Region.md) +* [域名](/docs/zh-CN/6-Host.md) +* [SSL 验证](/docs/zh-CN/7-Verify.md) +* [调试](/docs/zh-CN/8-Debug.md) +* [日志](/docs/zh-CN/9-Log.md) +* [测试](/docs/zh-CN/10-Test.md) + + +## 问题 +[提交 Issue](https://github.com/aliyun/openapi-sdk-php-client/issues/new/choose),不符合指南的问题可能会立即关闭。 + + +## 发行说明 +每个版本的详细更改记录在[发行说明](/CHANGELOG.md)中。 + + +## 贡献 +提交 Pull Request 之前请阅读[贡献指南](/CONTRIBUTING.md)。 + + +## 相关 +* [阿里云服务 Regions & Endpoints][endpoints] +* [OpenAPI Explorer][open-api] +* [Packagist][packagist] +* [Composer][composer] +* [Guzzle中文文档][guzzle-docs] +* [最新源码][latest-release] + + +## 许可证 +[Apache-2.0](/LICENSE.md) + +Copyright (c) 2009-present, Alibaba Cloud All rights reserved. + + +[SDK]: https://github.com/aliyun/openapi-sdk-php +[open-api]: https://api.aliyun.com +[latest-release]: https://github.com/aliyun/openapi-sdk-php-client +[guzzle-docs]: https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html +[composer]: https://getcomposer.org +[packagist]: https://packagist.org/packages/alibabacloud/sdk +[home]: https://home.console.aliyun.com +[aliyun]: https://www.aliyun.com +[regions]: https://help.aliyun.com/document_detail/40654.html +[endpoints]: https://developer.aliyun.com/endpoints +[cURL]: http://php.net/manual/zh/book.curl.php +[OPCache]: http://php.net/manual/zh/book.opcache.php +[xdebug]: http://xdebug.org +[OpenSSL]: http://php.net/manual/zh/book.openssl.php +[client]: https://github.com/aliyun/openapi-sdk-php-client diff --git a/vendor/alibabacloud/client/README.md b/vendor/alibabacloud/client/README.md new file mode 100644 index 0000000..764e71d --- /dev/null +++ b/vendor/alibabacloud/client/README.md @@ -0,0 +1,162 @@ +English | [简体中文](/README-zh-CN.md) + + +# Alibaba Cloud Client for PHP +[![Latest Stable Version](https://poser.pugx.org/alibabacloud/client/v/stable)](https://packagist.org/packages/alibabacloud/client) +[![composer.lock](https://poser.pugx.org/alibabacloud/client/composerlock)](https://packagist.org/packages/alibabacloud/client) +[![Total Downloads](https://poser.pugx.org/alibabacloud/client/downloads)](https://packagist.org/packages/alibabacloud/client) +[![License](https://poser.pugx.org/alibabacloud/client/license)](https://packagist.org/packages/alibabacloud/client) +[![codecov](https://codecov.io/gh/aliyun/openapi-sdk-php-client/branch/master/graph/badge.svg)](https://codecov.io/gh/aliyun/openapi-sdk-php-client) +[![Travis Build Status](https://travis-ci.org/aliyun/openapi-sdk-php-client.svg?branch=master)](https://travis-ci.org/aliyun/openapi-sdk-php-client) +[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/qb8j3lhg8349k0hk/branch/master?svg=true)](https://ci.appveyor.com/project/aliyun/openapi-sdk-php-client/branch/master) + + +![](https://aliyunsdk-pages.alicdn.com/icons/AlibabaCloud.svg) + + +Alibaba Cloud Client for PHP is a client tool that helps PHP developers manage credentials and send requests, [Alibaba Cloud SDK for PHP][SDK] dependency on this tool. + + +## Online Demo +[API Explorer](https://api.aliyun.com) provides the ability to call the cloud product OpenAPI online, and dynamically generate SDK Example code and quick retrieval interface, which can significantly reduce the difficulty of using the cloud API. + + +## Prerequisites +Your system will need to meet the [Prerequisites](/docs/en-US/0-Prerequisites.md), including having PHP >= 5.5. We highly recommend having it compiled with the cURL extension and cURL 7.16.2+. + + +## Installation +If Composer is already [installed globally on your system](https://getcomposer.org/doc/00-intro.md#globally), run the following in the base directory of your project to install Alibaba Cloud Client for PHP as a dependency: +``` +composer require alibabacloud/client +``` +> Some users may not be able to install due to network problems, you can try to switch the Composer mirror. + +Please see the [Installation](/docs/en-US/1-Installation.md) for more detailed information about installing the Alibaba Cloud Client for PHP through Composer and other means. + + +## Quick Examples +Before you begin, you need to sign up for an Alibaba Cloud account and retrieve your [Credentials](https://usercenter.console.aliyun.com/#/manage/ak). + +### Create Client +```php +asDefaultClient(); +``` + +### ROA Request +```php +regionId('cn-hangzhou') // Specify the requested regionId, if not specified, use the client regionId, then default regionId + ->product('CS') // Specify product + ->version('2015-12-15') // Specify product version + ->action('DescribeClusterServices') // Specify product interface + ->serviceCode('cs') // Set ServiceCode for addressing, optional + ->endpointType('openAPI') // Set type, optional + ->method('GET') // Set request method + ->host('cs.aliyun.com') // Location Service will not be enabled if the host is specified. For example, service with a Certification type-Bearer Token should be specified + ->pathPattern('/clusters/[ClusterId]/services') // Specify path rule with ROA-style + ->withClusterId('123456') // Assign values to parameters in the path. Method: with + Parameter + ->request(); // Make a request and return to result object. The request is to be placed at the end of the setting + + print_r($result->toArray()); + +} catch (ClientException $exception) { + print_r($exception->getErrorMessage()); +} catch (ServerException $exception) { + print_r($exception->getErrorMessage()); +} +``` + +### RPC Request +```php +product('Cdn') + ->version('2014-11-11') + ->action('DescribeCdnService') + ->method('POST') + ->request(); + + print_r($result->toArray()); + +} catch (ClientException $exception) { + print_r($exception->getErrorMessage()); +} catch (ServerException $exception) { + print_r($exception->getErrorMessage()); +} +``` + + +## Documentation +* [Prerequisites](/docs/en-US/0-Prerequisites.md) +* [Installation](/docs/en-US/1-Installation.md) +* [Client](/docs/en-US/2-Client.md) +* [Request](/docs/en-US/3-Request.md) +* [Result](/docs/en-US/4-Result.md) +* [Region](/docs/en-US/5-Region.md) +* [Host](/docs/en-US/6-Host.md) +* [SSL Verify](/docs/en-US/7-Verify.md) +* [Debug](/docs/en-US/8-Debug.md) +* [Log](/docs/en-US/9-Log.md) +* [Test](/docs/en-US/10-Test.md) + + +## Issues +[Opening an Issue](https://github.com/aliyun/openapi-sdk-php-client/issues/new/choose), Issues not conforming to the guidelines may be closed immediately. + + +## Changelog +Detailed changes for each release are documented in the [release notes](/CHANGELOG.md). + + +## Contribution +Please make sure to read the [Contributing Guide](/CONTRIBUTING.md) before making a pull request. + + +## References +* [Alibaba Cloud Regions & Endpoints][endpoints] +* [OpenAPI Explorer][open-api] +* [Packagist][packagist] +* [Composer][composer] +* [Guzzle Documentation][guzzle-docs] +* [Latest Release][latest-release] + + +## License +[Apache-2.0](/LICENSE.md) + +Copyright (c) 2009-present, Alibaba Cloud All rights reserved. + + +[SDK]: https://github.com/aliyun/openapi-sdk-php +[open-api]: https://api.alibabacloud.com +[latest-release]: https://github.com/aliyun/openapi-sdk-php-client +[guzzle-docs]: http://docs.guzzlephp.org/en/stable/request-options.html +[composer]: https://getcomposer.org +[packagist]: https://packagist.org/packages/alibabacloud/sdk +[home]: https://home.console.aliyun.com +[alibabacloud]: https://www.alibabacloud.com +[regions]: https://www.alibabacloud.com/help/doc-detail/40654.html +[endpoints]: https://developer.aliyun.com/endpoints +[cURL]: http://php.net/manual/en/book.curl.php +[OPCache]: http://php.net/manual/en/book.opcache.php +[xdebug]: http://xdebug.org +[OpenSSL]: http://php.net/manual/en/book.openssl.php +[client]: https://github.com/aliyun/openapi-sdk-php-client diff --git a/vendor/alibabacloud/client/UPGRADING.md b/vendor/alibabacloud/client/UPGRADING.md new file mode 100644 index 0000000..08c1bb3 --- /dev/null +++ b/vendor/alibabacloud/client/UPGRADING.md @@ -0,0 +1,6 @@ +Upgrading Guide +=============== + +1.x +----------------------- +- This is the first version. See for more information. diff --git a/vendor/alibabacloud/client/composer.json b/vendor/alibabacloud/client/composer.json new file mode 100644 index 0000000..0f5eb3c --- /dev/null +++ b/vendor/alibabacloud/client/composer.json @@ -0,0 +1,116 @@ +{ + "name": "alibabacloud/client", + "homepage": "https://www.alibabacloud.com/", + "description": "Alibaba Cloud Client for PHP - Use Alibaba Cloud in your PHP project", + "keywords": [ + "sdk", + "tool", + "cloud", + "client", + "aliyun", + "library", + "alibaba", + "alibabacloud" + ], + "type": "library", + "license": "Apache-2.0", + "support": { + "source": "https://github.com/aliyun/openapi-sdk-php-client", + "issues": "https://github.com/aliyun/openapi-sdk-php-client/issues" + }, + "authors": [ + { + "name": "Alibaba Cloud SDK", + "email": "sdk-team@alibabacloud.com", + "homepage": "http://www.alibabacloud.com" + } + ], + "require": { + "php": ">=5.5", + "ext-curl": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-openssl": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xmlwriter": "*", + "guzzlehttp/guzzle": "^6.3", + "danielstjules/stringy": "^3.1", + "mtdowling/jmespath.php": "^2.4", + "adbario/php-dot-notation": "^2.2", + "clagiordano/weblibs-configmanager": "^1.0" + }, + "require-dev": { + "ext-spl": "*", + "ext-dom": "*", + "ext-pcre": "*", + "psr/cache": "^1.0", + "ext-sockets": "*", + "drupal/coder": "^8.3", + "symfony/dotenv": "^3.4", + "league/climate": "^3.2.4", + "phpunit/phpunit": "^4.8.35|^5.4.3", + "monolog/monolog": "^1.24", + "composer/composer": "^1.8", + "mikey179/vfsstream": "^1.6", + "symfony/var-dumper": "^3.4" + }, + "suggest": { + "ext-sockets": "To use client-side monitoring" + }, + "autoload": { + "psr-4": { + "AlibabaCloud\\Client\\": "src" + }, + "files": [ + "src/Functions.php" + ] + }, + "autoload-dev": { + "psr-4": { + "AlibabaCloud\\Client\\Tests\\": "tests/" + } + }, + "config": { + "preferred-install": "dist", + "optimize-autoloader": true + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts-descriptions": { + "cs": "Tokenizes PHP, JavaScript and CSS files to detect violations of a defined coding standard.", + "cbf": "Automatically correct coding standard violations.", + "fixer": "Fixes code to follow standards.", + "test": "Run all tests.", + "unit": "Run Unit tests.", + "feature": "Run Feature tests.", + "clearCache": "Clear cache like coverage.", + "coverage": "Show Coverage html.", + "endpoints": "Update endpoints from OSS." + }, + "scripts": { + "cs": "phpcs --standard=PSR2 -n ./", + "cbf": "phpcbf --standard=PSR2 -n ./", + "fixer": "php-cs-fixer fix ./", + "test": [ + "phpunit --colors=always" + ], + "unit": [ + "@clearCache", + "phpunit --testsuite=Unit --colors=always" + ], + "feature": [ + "@clearCache", + "phpunit --testsuite=Feature --colors=always" + ], + "coverage": "open cache/coverage/index.html", + "clearCache": "rm -rf cache/*", + "endpoints": [ + "AlibabaCloud\\Client\\Regions\\LocationService::updateEndpoints", + "@fixer" + ], + "release": [ + "AlibabaCloud\\Client\\Release::release" + ] + } +} diff --git a/vendor/alibabacloud/client/src/Accept.php b/vendor/alibabacloud/client/src/Accept.php new file mode 100644 index 0000000..e7c5261 --- /dev/null +++ b/vendor/alibabacloud/client/src/Accept.php @@ -0,0 +1,53 @@ +format = $format; + } + + /** + * @param $format + * + * @return Accept + */ + public static function create($format) + { + return new static($format); + } + + /** + * @return mixed|string + */ + public function toString() + { + $key = \strtoupper($this->format); + + $list = [ + 'JSON' => 'application/json', + 'XML' => 'application/xml', + 'RAW' => 'application/octet-stream', + 'FORM' => 'application/x-www-form-urlencoded' + ]; + + return isset($list[$key]) ? $list[$key] : $list['RAW']; + } +} diff --git a/vendor/alibabacloud/client/src/AlibabaCloud.php b/vendor/alibabacloud/client/src/AlibabaCloud.php new file mode 100644 index 0000000..4bfd569 --- /dev/null +++ b/vendor/alibabacloud/client/src/AlibabaCloud.php @@ -0,0 +1,62 @@ +credential = $credential; + $this->signature = $signature; + $this->options['connect_timeout'] = Request::CONNECT_TIMEOUT; + $this->options['timeout'] = Request::TIMEOUT; + $this->options['verify'] = false; + } + + /** + * @return AccessKeyCredential|BearerTokenCredential|CredentialsInterface|EcsRamRoleCredential|RamRoleArnCredential|RsaKeyPairCredential|StsCredential + */ + public function getCredential() + { + return $this->credential; + } + + /** + * @return SignatureInterface|BearerTokenSignature|ShaHmac1Signature|ShaHmac256Signature|ShaHmac256WithRsaSignature + */ + public function getSignature() + { + return $this->signature; + } +} diff --git a/vendor/alibabacloud/client/src/Clients/EcsRamRoleClient.php b/vendor/alibabacloud/client/src/Clients/EcsRamRoleClient.php new file mode 100644 index 0000000..e97cd6c --- /dev/null +++ b/vendor/alibabacloud/client/src/Clients/EcsRamRoleClient.php @@ -0,0 +1,26 @@ +credential)) { + case EcsRamRoleCredential::class: + return (new EcsRamRoleProvider($this))->get(); + case RamRoleArnCredential::class: + return (new RamRoleArnProvider($this))->get($timeout, $connectTimeout); + case RsaKeyPairCredential::class: + return (new RsaKeyPairProvider($this))->get($timeout, $connectTimeout); + default: + return $this->credential; + } + } + + /** + * @return static + * @throws ClientException + * @deprecated + * @codeCoverageIgnore + */ + public function asGlobalClient() + { + return $this->asDefaultClient(); + } + + /** + * Set the current client as the default client. + * + * @return static + * @throws ClientException + */ + public function asDefaultClient() + { + return $this->name(CredentialsProvider::getDefaultName()); + } + + /** + * Naming clients. + * + * @param string $name + * + * @return static + * @throws ClientException + */ + public function name($name) + { + Filter::name($name); + + return AlibabaCloud::set($name, $this); + } + + /** + * @return bool + */ + public function isDebug() + { + if (isset($this->options['debug'])) { + return $this->options['debug'] === true && PHP_SAPI === 'cli'; + } + + return false; + } +} diff --git a/vendor/alibabacloud/client/src/Clients/RamRoleArnClient.php b/vendor/alibabacloud/client/src/Clients/RamRoleArnClient.php new file mode 100644 index 0000000..b7d3087 --- /dev/null +++ b/vendor/alibabacloud/client/src/Clients/RamRoleArnClient.php @@ -0,0 +1,33 @@ +getValue( + \strtolower($configPath), + $defaultValue + ); + } + + /** + * @return ConfigManager + */ + private static function getConfigManager() + { + if (!self::$configManager instanceof ConfigManager) { + self::$configManager = new ConfigManager(__DIR__ . DIRECTORY_SEPARATOR . 'Data.php'); + } + + return self::$configManager; + } + + /** + * @param string $configPath + * @param mixed $newValue + * + * @return ConfigManager + * @throws Exception + */ + public static function set($configPath, $newValue) + { + self::getConfigManager()->setValue(\strtolower($configPath), $newValue); + + return self::getConfigManager()->saveConfigFile(); + } +} diff --git a/vendor/alibabacloud/client/src/Config/Data.php b/vendor/alibabacloud/client/src/Config/Data.php new file mode 100644 index 0000000..dd74bac --- /dev/null +++ b/vendor/alibabacloud/client/src/Config/Data.php @@ -0,0 +1,3182 @@ + + [ + 'dysmsapi' => + [ + 'global' => 'dysmsapi.aliyuncs.com', + 'cn-hangzhou' => 'dysmsapi.aliyuncs.com', + 'ap-southeast-1' => 'dysmsapi.ap-southeast-1.aliyuncs.com', + ], + 'ccc' => + [ + 'global' => 'ccc.cn-shanghai.aliyuncs.com', + 'cn-shanghai' => 'ccc.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'ccc.cn-hangzhou.aliyuncs.com', + ], + 'dbs' => + [ + 'cn-hangzhou' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-qingdao' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'dbs-api.ap-southeast-1.aliyuncs.com', + 'ap-northeast-1' => 'dbs-api.ap-northeast-1.aliyuncs.com', + ], + 'dybaseapi' => + [ + 'global' => 'dybaseapi.aliyuncs.com', + 'cn-hangzhou' => 'dybaseapi.aliyuncs.com', + ], + 'dyiotapi' => + [ + 'global' => 'dyiotapi.aliyuncs.com', + 'cn-hangzhou' => 'dyiotapi.aliyuncs.com', + ], + 'dycdpapi' => + [ + 'global' => 'dycdpapi.aliyuncs.com', + 'cn-hangzhou' => 'dycdpapi.aliyuncs.com', + ], + 'dyplsapi' => + [ + 'global' => 'dyplsapi.aliyuncs.com', + 'cn-hangzhou' => 'dyplsapi.aliyuncs.com', + ], + 'dypnsapi' => + [ + 'global' => 'dypnsapi.aliyuncs.com', + 'cn-hangzhou' => 'dypnsapi.aliyuncs.com', + ], + 'dyvmsapi' => + [ + 'global' => 'dyvmsapi.aliyuncs.com', + 'cn-hangzhou' => 'dyvmsapi.aliyuncs.com', + ], + 'snsuapi' => + [ + 'global' => 'snsuapi.aliyuncs.com', + 'cn-hangzhou' => 'snsuapi.aliyuncs.com', + ], + 'ecs' => + [ + 'jp-fudao-1' => 'ecs-cn-hangzhou.aliyuncs.com', + 'me-east-1' => 'ecs.me-east-1.aliyuncs.com', + 'us-east-1' => 'ecs-cn-hangzhou.aliyuncs.com', + 'ap-northeast-1' => 'ecs.ap-northeast-1.aliyuncs.com', + 'cn-hangzhou-bj-b01' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-beijing-nu16-b01' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-beijing-am13-c01' => 'ecs-cn-hangzhou.aliyuncs.com', + 'in-west-antgroup-1' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-guizhou-gov' => 'ecs-cn-hangzhou.aliyuncs.com', + 'in-west-antgroup-2' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-qingdao-cm9' => 'ecs-cn-hangzhou.aliyuncs.com', + 'tw-snowcloud-kaohsiung' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-finance-1' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-guizhou' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-inner' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-fujian' => 'ecs-cn-hangzhou.aliyuncs.com', + 'in-mumbai-alipay' => 'ecs-cn-hangzhou.aliyuncs.com', + 'us-west-1' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-inner' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-anhui-gov-1' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-haidian-cm12-c01' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-anhui-gov' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'ecs-cn-hangzhou.aliyuncs.com', + 'ap-southeast-2' => 'ecs.ap-southeast-2.aliyuncs.com', + 'cn-qingdao' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-su18-b02' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-su18-b03' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-su18-b01' => 'ecs-cn-hangzhou.aliyuncs.com', + 'ap-southeast-antgroup-1' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-henan-am12001' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou-d' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-gansu-am6' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-ningxiazhongwei' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-ningxia-am7-c01' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'ecs-cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'ecs-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-st4-d01' => 'ecs-cn-hangzhou.aliyuncs.com', + 'eu-central-1' => 'ecs.eu-central-1.aliyuncs.com', + 'cn-zhangjiakou' => 'ecs.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'ecs.cn-huhehaote.aliyuncs.com', + 'ap-southeast-3' => 'ecs.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'ecs.ap-southeast-5.aliyuncs.com', + 'eu-west-1' => 'ecs.eu-west-1.aliyuncs.com', + 'ap-south-1' => 'ecs.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'ecs.cn-chengdu.aliyuncs.com', + 'cn-north-2-gov-1' => 'ecs.aliyuncs.com', + ], + 'rds' => + [ + 'me-east-1' => 'rds.me-east-1.aliyuncs.com', + 'us-east-1' => 'rds.aliyuncs.com', + 'ap-northeast-1' => 'rds.ap-northeast-1.aliyuncs.com', + 'cn-hongkong' => 'rds.aliyuncs.com', + 'cn-qingdao-cm9' => 'rds.aliyuncs.com', + 'cn-shanghai-finance-1' => 'rds.aliyuncs.com', + 'cn-beijing-gov-1' => 'rds.aliyuncs.com', + 'cn-shanghai' => 'rds.aliyuncs.com', + 'cn-shenzhen-inner' => 'rds.aliyuncs.com', + 'cn-fujian' => 'rds.aliyuncs.com', + 'us-west-1' => 'rds.aliyuncs.com', + 'cn-shanghai-inner' => 'rds.aliyuncs.com', + 'cn-hangzhou' => 'rds.aliyuncs.com', + 'cn-beijing-inner' => 'rds.aliyuncs.com', + 'cn-haidian-cm12-c01' => 'rds.aliyuncs.com', + 'cn-shenzhen' => 'rds.aliyuncs.com', + 'ap-southeast-2' => 'rds.ap-southeast-2.aliyuncs.com', + 'cn-qingdao' => 'rds.aliyuncs.com', + 'cn-beijing' => 'rds.aliyuncs.com', + 'cn-hangzhou-d' => 'rds.aliyuncs.com', + 'cn-gansu-am6' => 'rds.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'rds.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'rds.aliyuncs.com', + 'ap-southeast-1' => 'rds.aliyuncs.com', + 'eu-central-1' => 'rds.eu-central-1.aliyuncs.com', + 'cn-zhangjiakou' => 'rds.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'rds.cn-huhehaote.aliyuncs.com', + 'ap-southeast-3' => 'rds.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'rds.ap-southeast-5.aliyuncs.com', + 'eu-west-1' => 'rds.eu-west-1.aliyuncs.com', + 'ap-south-1' => 'rds.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'rds.cn-chengdu.aliyuncs.com', + 'cn-north-2-gov-1' => 'rds.aliyuncs.com', + ], + 'vpc' => + [ + 'me-east-1' => 'vpc.me-east-1.aliyuncs.com', + 'us-east-1' => 'vpc.aliyuncs.com', + 'ap-northeast-1' => 'vpc.ap-northeast-1.aliyuncs.com', + 'cn-hongkong' => 'vpc.aliyuncs.com', + 'cn-beijing-am13-c01' => 'vpc.aliyuncs.com', + 'cn-guizhou-gov' => 'vpc.aliyuncs.com', + 'cn-shanghai-finance-1' => 'vpc.aliyuncs.com', + 'cn-guizhou' => 'vpc.aliyuncs.com', + 'cn-shanghai' => 'vpc.aliyuncs.com', + 'us-west-1' => 'vpc.aliyuncs.com', + 'cn-hangzhou' => 'vpc.aliyuncs.com', + 'cn-haidian-cm12-c01' => 'vpc.aliyuncs.com', + 'cn-anhui-gov' => 'vpc.aliyuncs.com', + 'cn-shenzhen' => 'vpc.aliyuncs.com', + 'ap-southeast-2' => 'vpc.ap-southeast-2.aliyuncs.com', + 'cn-henan-am12001' => 'vpc.aliyuncs.com', + 'cn-beijing' => 'vpc.aliyuncs.com', + 'cn-gansu-am6' => 'vpc.aliyuncs.com', + 'cn-ningxiazhongwei' => 'vpc.aliyuncs.com', + 'cn-ningxia-am7-c01' => 'vpc.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'vpc.aliyuncs.com', + 'ap-southeast-1' => 'vpc.aliyuncs.com', + 'eu-central-1' => 'vpc.eu-central-1.aliyuncs.com', + 'cn-zhangjiakou' => 'vpc.cn-zhangjiakou.aliyuncs.com', + 'cn-qingdao' => 'vpc.aliyuncs.com', + 'cn-huhehaote' => 'vpc.cn-huhehaote.aliyuncs.com', + 'ap-southeast-3' => 'vpc.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'vpc.ap-southeast-5.aliyuncs.com', + 'eu-west-1' => 'vpc.eu-west-1.aliyuncs.com', + 'ap-south-1' => 'vpc.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'vpc.cn-chengdu.aliyuncs.com', + 'cn-north-2-gov-1' => 'vpc.aliyuncs.com', + ], + 'kms' => + [ + 'me-east-1' => 'kms.me-east-1.aliyuncs.com', + 'ap-northeast-1' => 'kms.ap-northeast-1.aliyuncs.com', + 'cn-hongkong' => 'kms.cn-hongkong.aliyuncs.com', + 'cn-shanghai-finance-1' => 'kms.cn-shanghai-finance-1.aliyuncs.com', + 'cn-shanghai' => 'kms.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'kms.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'kms.cn-shenzhen.aliyuncs.com', + 'ap-southeast-2' => 'kms.ap-southeast-2.aliyuncs.com', + 'cn-beijing' => 'kms.cn-beijing.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'kms.cn-shenzhen-finance-1.aliyuncs.com', + 'ap-southeast-1' => 'kms.ap-southeast-1.aliyuncs.com', + 'eu-central-1' => 'kms.eu-central-1.aliyuncs.com', + 'cn-qingdao' => 'kms.cn-qingdao.aliyuncs.com', + 'cn-zhangjiakou' => 'kms.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'kms.cn-huhehaote.aliyuncs.com', + 'ap-southeast-3' => 'kms.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'kms.ap-southeast-5.aliyuncs.com', + 'eu-west-1' => 'kms.eu-west-1.aliyuncs.com', + 'us-west-1' => 'kms.us-west-1.aliyuncs.com', + 'us-east-1' => 'kms.us-east-1.aliyuncs.com', + 'ap-south-1' => 'kms.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'kms.cn-chengdu.aliyuncs.com', + 'cn-hangzhou-finance' => 'kms.cn-hangzhou-finance.aliyuncs.com', + 'cn-north-2-gov-1' => 'kms.cn-north-2-gov-1.aliyuncs.com', + ], + 'cms' => + [ + 'me-east-1' => 'metrics.cn-hangzhou.aliyuncs.com', + 'us-east-1' => 'metrics.cn-hangzhou.aliyuncs.com', + 'ap-northeast-1' => 'metrics.ap-northeast-1.aliyuncs.com', + 'cn-hongkong' => 'metrics.cn-hangzhou.aliyuncs.com', + 'cn-qingdao-cm9' => 'metrics.aliyuncs.com', + 'cn-shanghai' => 'metrics.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-inner' => 'metrics.aliyuncs.com', + 'us-west-1' => 'metrics.cn-hangzhou.aliyuncs.com', + 'cn-shanghai-inner' => 'metrics.aliyuncs.com', + 'cn-hangzhou' => 'metrics.cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'metrics.aliyuncs.com', + 'cn-shenzhen' => 'metrics.cn-hangzhou.aliyuncs.com', + 'ap-southeast-2' => 'metrics.cn-hangzhou.aliyuncs.com', + 'cn-qingdao' => 'metrics.cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'metrics.cn-hangzhou.aliyuncs.com', + 'cn-hangzhou-d' => 'metrics.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'metrics.aliyuncs.com', + 'ap-southeast-1' => 'metrics.cn-hangzhou.aliyuncs.com', + 'eu-central-1' => 'metrics.cn-hangzhou.aliyuncs.com', + 'cn-zhangjiakou' => 'metrics.cn-hangzhou.aliyuncs.com', + 'cn-huhehaote' => 'metrics.cn-huhehaote.aliyuncs.com', + 'ap-southeast-3' => 'metrics.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'metrics.ap-southeast-5.aliyuncs.com', + 'eu-west-1' => 'metrics.eu-west-1.aliyuncs.com', + 'ap-south-1' => 'metrics.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'metrics.cn-chengdu.aliyuncs.com', + 'cn-shanghai-finance-1' => 'metrics.cn-shanghai-finance-1.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'metrics.cn-shenzhen-finance-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'metrics.cn-north-2-gov-1.aliyuncs.com', + ], + 'slb' => + [ + 'me-east-1' => 'slb.me-east-1.aliyuncs.com', + 'us-east-1' => 'slb.aliyuncs.com', + 'ap-northeast-1' => 'slb.ap-northeast-1.aliyuncs.com', + 'cn-hongkong' => 'slb.aliyuncs.com', + 'cn-qingdao-cm9' => 'slb.aliyuncs.com', + 'cn-shanghai' => 'slb.aliyuncs.com', + 'cn-shenzhen-inner' => 'slb.aliyuncs.com', + 'us-west-1' => 'slb.aliyuncs.com', + 'cn-shanghai-inner' => 'slb.aliyuncs.com', + 'cn-hangzhou' => 'slb.aliyuncs.com', + 'cn-beijing-inner' => 'slb.aliyuncs.com', + 'cn-shenzhen' => 'slb.aliyuncs.com', + 'ap-southeast-2' => 'slb.ap-southeast-2.aliyuncs.com', + 'cn-qingdao' => 'slb.aliyuncs.com', + 'cn-beijing' => 'slb.aliyuncs.com', + 'cn-hangzhou-d' => 'slb.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'slb.aliyuncs.com', + 'ap-southeast-1' => 'slb.aliyuncs.com', + 'eu-central-1' => 'slb.eu-central-1.aliyuncs.com', + 'cn-zhangjiakou' => 'slb.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'slb.cn-huhehaote.aliyuncs.com', + 'ap-southeast-3' => 'slb.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'slb.ap-southeast-5.aliyuncs.com', + 'eu-west-1' => 'slb.eu-west-1.aliyuncs.com', + 'ap-south-1' => 'slb.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'slb.cn-chengdu.aliyuncs.com', + 'cn-shanghai-finance-1' => 'slb.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'slb.aliyuncs.com', + 'cn-north-2-gov-1' => 'slb.aliyuncs.com', + ], + 'cs' => + [ + 'us-east-1' => 'cs.us-east-1.aliyuncs.com', + 'cn-hongkong' => 'cs.cn-hongkong.aliyuncs.com', + 'cn-qingdao-cm9' => 'cs.aliyuncs.com', + 'cn-shanghai' => 'cs.cn-shanghai.aliyuncs.com', + 'cn-shenzhen-inner' => 'cs.aliyuncs.com', + 'us-west-1' => 'cs.us-west-1.aliyuncs.com', + 'cn-shanghai-inner' => 'cs.aliyuncs.com', + 'cn-hangzhou' => 'cs.cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'cs.aliyuncs.com', + 'cn-shenzhen' => 'cs.cn-shenzhen.aliyuncs.com', + 'cn-qingdao' => 'cs.aliyuncs.com', + 'cn-beijing' => 'cs.cn-beijing.aliyuncs.com', + 'cn-hangzhou-d' => 'cs.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'cs.aliyuncs.com', + 'ap-southeast-1' => 'cs.ap-southeast-1.aliyuncs.com', + 'cn-chengdu' => 'cs.cn-chengdu.aliyuncs.com', + 'cn-zhangjiakou' => 'cs.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'cs.cn-huhehaote.aliyuncs.com', + 'ap-southeast-2' => 'cs.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'cs.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'cs.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'cs.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'cs.eu-west-1.aliyuncs.com', + 'eu-central-1' => 'cs.eu-central-1.aliyuncs.com', + 'me-east-1' => 'cs.me-east-1.aliyuncs.com', + 'ap-south-1' => 'cs.ap-south-1.aliyuncs.com', + 'cn-shanghai-finance-1' => 'cs.cn-shanghai-finance-1.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'cs.cn-shenzhen-finance-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'cs.cn-north-2-gov-1.aliyuncs.com', + ], + 'push' => + [ + 'us-east-1' => 'cloudpush.aliyuncs.com', + 'cn-hongkong' => 'cloudpush.aliyuncs.com', + 'cn-qingdao-cm9' => 'cloudpush.aliyuncs.com', + 'cn-shanghai' => 'cloudpush.aliyuncs.com', + 'cn-shenzhen-inner' => 'cloudpush.aliyuncs.com', + 'us-west-1' => 'cloudpush.aliyuncs.com', + 'cn-shanghai-inner' => 'cloudpush.aliyuncs.com', + 'cn-hangzhou' => 'cloudpush.aliyuncs.com', + 'cn-beijing-inner' => 'cloudpush.aliyuncs.com', + 'cn-shenzhen' => 'cloudpush.aliyuncs.com', + 'cn-qingdao' => 'cloudpush.aliyuncs.com', + 'cn-beijing' => 'cloudpush.aliyuncs.com', + 'cn-hangzhou-d' => 'cloudpush.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'cloudpush.aliyuncs.com', + 'ap-southeast-1' => 'cloudpush.aliyuncs.com', + ], + 'cos' => + [ + 'us-east-1' => 'cos.aliyuncs.com', + 'cn-hongkong' => 'cos.aliyuncs.com', + 'cn-qingdao-cm9' => 'cos.aliyuncs.com', + 'cn-shanghai' => 'cos.aliyuncs.com', + 'cn-shenzhen-inner' => 'cos.aliyuncs.com', + 'us-west-1' => 'cos.aliyuncs.com', + 'cn-shanghai-inner' => 'cos.aliyuncs.com', + 'cn-hangzhou' => 'cos.aliyuncs.com', + 'cn-beijing-inner' => 'cos.aliyuncs.com', + 'cn-shenzhen' => 'cos.aliyuncs.com', + 'cn-qingdao' => 'cos.aliyuncs.com', + 'cn-beijing' => 'cos.aliyuncs.com', + 'cn-hangzhou-d' => 'cos.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'cos.aliyuncs.com', + 'ap-southeast-1' => 'cos.aliyuncs.com', + ], + 'ess' => + [ + 'us-east-1' => 'ess.aliyuncs.com', + 'cn-hongkong' => 'ess.aliyuncs.com', + 'cn-qingdao-cm9' => 'ess.aliyuncs.com', + 'cn-shanghai' => 'ess.aliyuncs.com', + 'cn-shenzhen-inner' => 'ess.aliyuncs.com', + 'us-west-1' => 'ess.aliyuncs.com', + 'cn-shanghai-inner' => 'ess.aliyuncs.com', + 'cn-hangzhou' => 'ess.aliyuncs.com', + 'cn-beijing-inner' => 'ess.aliyuncs.com', + 'cn-shenzhen' => 'ess.aliyuncs.com', + 'cn-qingdao' => 'ess.aliyuncs.com', + 'cn-beijing' => 'ess.aliyuncs.com', + 'cn-hangzhou-d' => 'ess.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'ess.aliyuncs.com', + 'ap-southeast-1' => 'ess.aliyuncs.com', + 'cn-zhangjiakou' => 'ess.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'ess.cn-huhehaote.aliyuncs.com', + 'ap-southeast-2' => 'ess.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'ess.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'ess.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'ess.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'ess.eu-west-1.aliyuncs.com', + 'eu-central-1' => 'ess.eu-central-1.aliyuncs.com', + 'me-east-1' => 'ess.me-east-1.aliyuncs.com', + 'ap-south-1' => 'ess.ap-south-1.aliyuncs.com', + 'cn-shanghai-finance-1' => 'ess.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'ess.aliyuncs.com', + 'cn-north-2-gov-1' => 'ess.aliyuncs.com', + 'cn-chengdu' => 'ess.cn-chengdu.aliyuncs.com', + ], + 'ace-ops' => + [ + 'us-east-1' => 'ace-ops.cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'ace-ops.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'ace-ops.cn-hangzhou.aliyuncs.com', + 'us-west-1' => 'ace-ops.cn-hangzhou.aliyuncs.com', + 'cn-hangzhou' => 'ace-ops.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'ace-ops.cn-hangzhou.aliyuncs.com', + 'cn-qingdao' => 'ace-ops.cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'ace-ops.cn-hangzhou.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'ace-ops.cn-hangzhou.aliyuncs.com', + ], + 'billing' => + [ + 'us-east-1' => 'billing.aliyuncs.com', + 'cn-hongkong' => 'billing.aliyuncs.com', + 'cn-qingdao-cm9' => 'billing.aliyuncs.com', + 'cn-shanghai' => 'billing.aliyuncs.com', + 'cn-shenzhen-inner' => 'billing.aliyuncs.com', + 'us-west-1' => 'billing.aliyuncs.com', + 'cn-shanghai-inner' => 'billing.aliyuncs.com', + 'cn-hangzhou' => 'billing.aliyuncs.com', + 'cn-beijing-inner' => 'billing.aliyuncs.com', + 'cn-beijing' => 'billing.aliyuncs.com', + 'cn-hangzhou-d' => 'billing.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'billing.aliyuncs.com', + 'ap-southeast-1' => 'billing.aliyuncs.com', + ], + 'dqs' => + [ + 'us-east-1' => 'dqs.aliyuncs.com', + 'cn-hongkong' => 'dqs.aliyuncs.com', + 'cn-qingdao-cm9' => 'dqs.aliyuncs.com', + 'cn-shanghai' => 'dqs.aliyuncs.com', + 'cn-shenzhen-inner' => 'dqs.aliyuncs.com', + 'us-west-1' => 'dqs.aliyuncs.com', + 'cn-shanghai-inner' => 'dqs.aliyuncs.com', + 'cn-hangzhou' => 'dqs.aliyuncs.com', + 'cn-beijing-inner' => 'dqs.aliyuncs.com', + 'cn-shenzhen' => 'dqs.aliyuncs.com', + 'cn-qingdao' => 'dqs.aliyuncs.com', + 'cn-beijing' => 'dqs.aliyuncs.com', + 'cn-hangzhou-d' => 'dqs.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'dqs.aliyuncs.com', + 'ap-southeast-1' => 'dqs.aliyuncs.com', + ], + 'dds' => + [ + 'us-east-1' => 'mongodb.aliyuncs.com', + 'cn-hongkong' => 'mongodb.aliyuncs.com', + 'cn-qingdao-cm9' => 'mongodb.aliyuncs.com', + 'cn-shanghai' => 'mongodb.aliyuncs.com', + 'cn-shenzhen-inner' => 'mongodb.aliyuncs.com', + 'us-west-1' => 'mongodb.aliyuncs.com', + 'cn-shanghai-inner' => 'mongodb.aliyuncs.com', + 'cn-hangzhou' => 'mongodb.aliyuncs.com', + 'cn-beijing-inner' => 'mongodb.aliyuncs.com', + 'cn-shenzhen' => 'mongodb.aliyuncs.com', + 'cn-qingdao' => 'mongodb.aliyuncs.com', + 'cn-beijing' => 'mongodb.aliyuncs.com', + 'cn-hangzhou-d' => 'mongodb.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'mongodb.aliyuncs.com', + 'ap-southeast-1' => 'mongodb.aliyuncs.com', + 'cn-zhangjiakou' => 'mongodb.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'mongodb.cn-huhehaote.aliyuncs.com', + 'ap-southeast-2' => 'mongodb.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'mongodb.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'mongodb.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'mongodb.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'mongodb.eu-west-1.aliyuncs.com', + 'eu-central-1' => 'mongodb.eu-central-1.aliyuncs.com', + 'me-east-1' => 'mongodb.me-east-1.aliyuncs.com', + 'ap-south-1' => 'mongodb.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'mongodb.cn-chengdu.aliyuncs.com', + 'cn-hangzhou-finance' => 'mongodb.aliyuncs.com', + 'cn-shanghai-finance-1' => 'mongodb.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'mongodb.aliyuncs.com', + 'cn-north-2-gov-1' => 'mongodb.aliyuncs.com', + ], + 'emr' => + [ + 'us-east-1' => 'emr.us-east-1.aliyuncs.com', + 'cn-hongkong' => 'emr.cn-hongkong.aliyuncs.com', + 'cn-qingdao-cm9' => 'emr.aliyuncs.com', + 'cn-shanghai' => 'emr.aliyuncs.com', + 'cn-shenzhen-inner' => 'emr.aliyuncs.com', + 'us-west-1' => 'emr.aliyuncs.com', + 'cn-shanghai-inner' => 'emr.aliyuncs.com', + 'cn-hangzhou' => 'emr.aliyuncs.com', + 'cn-beijing-inner' => 'emr.aliyuncs.com', + 'cn-shenzhen' => 'emr.aliyuncs.com', + 'cn-qingdao' => 'emr.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'emr.aliyuncs.com', + 'cn-hangzhou-d' => 'emr.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'emr.aliyuncs.com', + 'ap-southeast-1' => 'emr.aliyuncs.com', + 'cn-zhangjiakou' => 'emr.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'emr.cn-huhehaote.aliyuncs.com', + 'ap-southeast-2' => 'emr.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'emr.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'emr.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'emr.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'emr.eu-west-1.aliyuncs.com', + 'eu-central-1' => 'emr.eu-central-1.aliyuncs.com', + 'me-east-1' => 'emr.me-east-1.aliyuncs.com', + 'ap-south-1' => 'emr.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'emr.cn-chengdu.aliyuncs.com', + ], + 'sms' => + [ + 'us-east-1' => 'sms.aliyuncs.com', + 'cn-hongkong' => 'sms.aliyuncs.com', + 'cn-qingdao-cm9' => 'sms.aliyuncs.com', + 'cn-shanghai' => 'sms.aliyuncs.com', + 'cn-shenzhen-inner' => 'sms.aliyuncs.com', + 'us-west-1' => 'sms.aliyuncs.com', + 'cn-shanghai-inner' => 'sms.aliyuncs.com', + 'cn-hangzhou' => 'sms.aliyuncs.com', + 'cn-beijing-inner' => 'sms.aliyuncs.com', + 'cn-shenzhen' => 'sms.aliyuncs.com', + 'cn-qingdao' => 'sms.aliyuncs.com', + 'cn-beijing' => 'sms.aliyuncs.com', + 'cn-hangzhou-d' => 'sms.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'sms.aliyuncs.com', + 'ap-southeast-1' => 'sms.aliyuncs.com', + ], + 'jaq' => + [ + 'us-east-1' => 'jaq.aliyuncs.com', + 'cn-hongkong' => 'jaq.aliyuncs.com', + 'cn-qingdao-cm9' => 'jaq.aliyuncs.com', + 'cn-shanghai' => 'jaq.aliyuncs.com', + 'cn-shenzhen-inner' => 'jaq.aliyuncs.com', + 'us-west-1' => 'jaq.aliyuncs.com', + 'cn-shanghai-inner' => 'jaq.aliyuncs.com', + 'cn-hangzhou' => 'jaq.aliyuncs.com', + 'cn-beijing-inner' => 'jaq.aliyuncs.com', + 'cn-shenzhen' => 'jaq.aliyuncs.com', + 'cn-qingdao' => 'jaq.aliyuncs.com', + 'cn-beijing' => 'jaq.aliyuncs.com', + 'cn-hangzhou-d' => 'jaq.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'jaq.aliyuncs.com', + 'ap-southeast-1' => 'jaq.aliyuncs.com', + ], + 'hpc' => + [ + 'us-east-1' => 'hpc.aliyuncs.com', + 'cn-hongkong' => 'hpc.aliyuncs.com', + 'cn-qingdao-cm9' => 'hpc.aliyuncs.com', + 'cn-shanghai' => 'hpc.aliyuncs.com', + 'cn-shenzhen-inner' => 'hpc.aliyuncs.com', + 'us-west-1' => 'hpc.aliyuncs.com', + 'cn-shanghai-inner' => 'hpc.aliyuncs.com', + 'cn-hangzhou' => 'hpc.aliyuncs.com', + 'cn-beijing-inner' => 'hpc.aliyuncs.com', + 'cn-shenzhen' => 'hpc.aliyuncs.com', + 'cn-qingdao' => 'hpc.aliyuncs.com', + 'cn-beijing' => 'hpc.aliyuncs.com', + 'cn-hangzhou-d' => 'hpc.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'hpc.aliyuncs.com', + 'ap-southeast-1' => 'hpc.aliyuncs.com', + ], + 'location' => + [ + 'us-east-1' => 'location.aliyuncs.com', + 'cn-hongkong' => 'location.aliyuncs.com', + 'cn-qingdao-cm9' => 'location.aliyuncs.com', + 'cn-shanghai' => 'location.aliyuncs.com', + 'cn-shenzhen-inner' => 'location.aliyuncs.com', + 'us-west-1' => 'location.aliyuncs.com', + 'cn-shanghai-inner' => 'location.aliyuncs.com', + 'cn-hangzhou' => 'location.aliyuncs.com', + 'cn-beijing-inner' => 'location.aliyuncs.com', + 'cn-shenzhen' => 'location.aliyuncs.com', + 'cn-qingdao' => 'location.aliyuncs.com', + 'cn-beijing' => 'location.aliyuncs.com', + 'cn-hangzhou-d' => 'location.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'location.aliyuncs.com', + 'ap-southeast-1' => 'location.aliyuncs.com', + ], + 'chargingservice' => + [ + 'us-east-1' => 'chargingservice.aliyuncs.com', + 'cn-hongkong' => 'chargingservice.aliyuncs.com', + 'cn-qingdao-cm9' => 'chargingservice.aliyuncs.com', + 'cn-shanghai' => 'chargingservice.aliyuncs.com', + 'cn-shenzhen-inner' => 'chargingservice.aliyuncs.com', + 'us-west-1' => 'chargingservice.aliyuncs.com', + 'cn-shanghai-inner' => 'chargingservice.aliyuncs.com', + 'cn-hangzhou' => 'chargingservice.aliyuncs.com', + 'cn-beijing-inner' => 'chargingservice.aliyuncs.com', + 'cn-beijing' => 'chargingservice.aliyuncs.com', + 'cn-hangzhou-d' => 'chargingservice.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'chargingservice.aliyuncs.com', + 'ap-southeast-1' => 'chargingservice.aliyuncs.com', + ], + 'msg' => + [ + 'us-east-1' => 'msg-inner.aliyuncs.com', + 'cn-hongkong' => 'msg-inner.aliyuncs.com', + 'cn-qingdao-cm9' => 'msg-inner.aliyuncs.com', + 'cn-shanghai' => 'msg-inner.aliyuncs.com', + 'cn-shenzhen-inner' => 'msg-inner.aliyuncs.com', + 'us-west-1' => 'msg-inner.aliyuncs.com', + 'cn-shanghai-inner' => 'msg-inner.aliyuncs.com', + 'cn-hangzhou' => 'msg-inner.aliyuncs.com', + 'cn-beijing-inner' => 'msg-inner.aliyuncs.com', + 'cn-beijing' => 'msg-inner.aliyuncs.com', + 'cn-hangzhou-d' => 'msg-inner.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'msg-inner.aliyuncs.com', + 'ap-southeast-1' => 'msg-inner.aliyuncs.com', + ], + 'commondriver' => + [ + 'us-east-1' => 'common.driver.aliyuncs.com', + 'cn-hongkong' => 'common.driver.aliyuncs.com', + 'cn-qingdao-cm9' => 'common.driver.aliyuncs.com', + 'cn-shanghai' => 'common.driver.aliyuncs.com', + 'cn-shenzhen-inner' => 'common.driver.aliyuncs.com', + 'us-west-1' => 'common.driver.aliyuncs.com', + 'cn-shanghai-inner' => 'common.driver.aliyuncs.com', + 'cn-hangzhou' => 'common.driver.aliyuncs.com', + 'cn-beijing-inner' => 'common.driver.aliyuncs.com', + 'cn-shenzhen' => 'common.driver.aliyuncs.com', + 'cn-qingdao' => 'common.driver.aliyuncs.com', + 'cn-beijing' => 'common.driver.aliyuncs.com', + 'cn-hangzhou-d' => 'common.driver.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'common.driver.aliyuncs.com', + 'ap-southeast-1' => 'common.driver.aliyuncs.com', + ], + 'r-kvstore' => + [ + 'us-east-1' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'cn-qingdao-cm9' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-inner' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'us-west-1' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-inner' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'cn-qingdao' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou-d' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'r-kvstore-cn-hangzhou.aliyuncs.com', + ], + 'bss' => + [ + 'us-east-1' => 'bss.aliyuncs.com', + 'cn-hongkong' => 'bss.aliyuncs.com', + 'cn-qingdao-cm9' => 'bss.aliyuncs.com', + 'cn-shanghai' => 'bss.aliyuncs.com', + 'cn-shenzhen-inner' => 'bss.aliyuncs.com', + 'us-west-1' => 'bss.aliyuncs.com', + 'cn-shanghai-inner' => 'bss.aliyuncs.com', + 'cn-hangzhou' => 'bss.aliyuncs.com', + 'cn-beijing-inner' => 'bss.aliyuncs.com', + 'cn-shenzhen' => 'bss.aliyuncs.com', + 'cn-qingdao' => 'bss.aliyuncs.com', + 'cn-beijing' => 'bss.aliyuncs.com', + 'cn-hangzhou-d' => 'bss.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'bss.aliyuncs.com', + 'ap-southeast-1' => 'bss.aliyuncs.com', + ], + 'workorder' => + [ + 'us-east-1' => 'workorder.aliyuncs.com', + 'cn-hongkong' => 'workorder.aliyuncs.com', + 'cn-qingdao-cm9' => 'workorder.aliyuncs.com', + 'cn-shanghai' => 'workorder.aliyuncs.com', + 'cn-shenzhen-inner' => 'workorder.aliyuncs.com', + 'us-west-1' => 'workorder.aliyuncs.com', + 'cn-shanghai-inner' => 'workorder.aliyuncs.com', + 'cn-hangzhou' => 'workorder.aliyuncs.com', + 'cn-beijing-inner' => 'workorder.aliyuncs.com', + 'cn-beijing' => 'workorder.aliyuncs.com', + 'cn-hangzhou-d' => 'workorder.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'workorder.aliyuncs.com', + 'ap-southeast-1' => 'workorder.aliyuncs.com', + ], + 'ocs' => + [ + 'us-east-1' => 'm-kvstore.aliyuncs.com', + 'cn-hongkong' => 'm-kvstore.aliyuncs.com', + 'cn-qingdao-cm9' => 'm-kvstore.aliyuncs.com', + 'cn-shanghai' => 'm-kvstore.aliyuncs.com', + 'cn-shenzhen-inner' => 'm-kvstore.aliyuncs.com', + 'us-west-1' => 'm-kvstore.aliyuncs.com', + 'cn-shanghai-inner' => 'm-kvstore.aliyuncs.com', + 'cn-hangzhou' => 'm-kvstore.aliyuncs.com', + 'cn-beijing-inner' => 'm-kvstore.aliyuncs.com', + 'cn-shenzhen' => 'm-kvstore.aliyuncs.com', + 'cn-qingdao' => 'm-kvstore.aliyuncs.com', + 'cn-beijing' => 'm-kvstore.aliyuncs.com', + 'cn-hangzhou-d' => 'm-kvstore.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'm-kvstore.aliyuncs.com', + 'ap-southeast-1' => 'm-kvstore.aliyuncs.com', + ], + 'yundun' => + [ + 'us-east-1' => 'yundun-cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'yundun-cn-hangzhou.aliyuncs.com', + 'cn-qingdao-cm9' => 'yundun-cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'yundun-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-inner' => 'yundun-cn-hangzhou.aliyuncs.com', + 'us-west-1' => 'yundun-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-inner' => 'yundun-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou' => 'yundun-cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'yundun-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'yundun-cn-hangzhou.aliyuncs.com', + 'cn-qingdao' => 'yundun-cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'yundun-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou-d' => 'yundun-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'yundun-cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'yundun-cn-hangzhou.aliyuncs.com', + ], + 'ubsms-inner' => + [ + 'us-east-1' => 'ubsms-inner.aliyuncs.com', + 'cn-hongkong' => 'ubsms-inner.aliyuncs.com', + 'cn-qingdao-cm9' => 'ubsms-inner.aliyuncs.com', + 'cn-shanghai' => 'ubsms-inner.aliyuncs.com', + 'cn-shenzhen-inner' => 'ubsms-inner.aliyuncs.com', + 'us-west-1' => 'ubsms-inner.aliyuncs.com', + 'cn-shanghai-inner' => 'ubsms-inner.aliyuncs.com', + 'cn-hangzhou' => 'ubsms-inner.aliyuncs.com', + 'cn-beijing-inner' => 'ubsms-inner.aliyuncs.com', + 'cn-shenzhen' => 'ubsms-inner.aliyuncs.com', + 'cn-qingdao' => 'ubsms-inner.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'ubsms-inner.aliyuncs.com', + 'cn-hangzhou-d' => 'ubsms-inner.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'ubsms-inner.aliyuncs.com', + 'ap-southeast-1' => 'ubsms-inner.aliyuncs.com', + ], + 'dm' => + [ + 'us-east-1' => 'dm.aliyuncs.com', + 'cn-hongkong' => 'dm.aliyuncs.com', + 'cn-qingdao-cm9' => 'dm.aliyuncs.com', + 'cn-shanghai' => 'dm.aliyuncs.com', + 'cn-shenzhen-inner' => 'dm.aliyuncs.com', + 'us-west-1' => 'dm.aliyuncs.com', + 'cn-shanghai-inner' => 'dm.aliyuncs.com', + 'cn-hangzhou' => 'dm.aliyuncs.com', + 'cn-beijing-inner' => 'dm.aliyuncs.com', + 'cn-shenzhen' => 'dm.aliyuncs.com', + 'cn-qingdao' => 'dm.aliyuncs.com', + 'cn-beijing' => 'dm.aliyuncs.com', + 'cn-hangzhou-d' => 'dm.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'dm.aliyuncs.com', + 'ap-southeast-1' => 'dm.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'dm.ap-southeast-2.aliyuncs.com', + ], + 'green' => + [ + 'us-east-1' => 'green.aliyuncs.com', + 'cn-hongkong' => 'green.aliyuncs.com', + 'cn-qingdao-cm9' => 'green.aliyuncs.com', + 'cn-shanghai' => 'green.cn-shanghai.aliyuncs.com', + 'cn-shenzhen-inner' => 'green.aliyuncs.com', + 'us-west-1' => 'green.us-west-1.aliyuncs.com', + 'cn-shanghai-inner' => 'green.aliyuncs.com', + 'cn-hangzhou' => 'green.cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'green.aliyuncs.com', + 'cn-shenzhen' => 'green.cn-shenzhen.aliyuncs.com', + 'cn-qingdao' => 'green.aliyuncs.com', + 'cn-beijing' => 'green.cn-beijing.aliyuncs.com', + 'cn-hangzhou-d' => 'green.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'green.aliyuncs.com', + 'ap-southeast-1' => 'green.ap-southeast-1.aliyuncs.com', + ], + 'risk' => + [ + 'us-east-1' => 'risk-cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'risk-cn-hangzhou.aliyuncs.com', + 'cn-qingdao-cm9' => 'risk-cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'risk-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-inner' => 'risk-cn-hangzhou.aliyuncs.com', + 'us-west-1' => 'risk-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-inner' => 'risk-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou' => 'risk-cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'risk-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'risk-cn-hangzhou.aliyuncs.com', + 'cn-qingdao' => 'risk-cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'risk-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou-d' => 'risk-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'risk-cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'risk-cn-hangzhou.aliyuncs.com', + ], + 'oceanbase' => + [ + 'us-east-1' => 'oceanbase.aliyuncs.com', + 'cn-hongkong' => 'oceanbase.aliyuncs.com', + 'cn-qingdao-cm9' => 'oceanbase.aliyuncs.com', + 'cn-shanghai' => 'oceanbasepro-share.cn-shanghai.aliyuncs.com', + 'cn-shenzhen-inner' => 'oceanbase.aliyuncs.com', + 'us-west-1' => 'oceanbase.aliyuncs.com', + 'cn-shanghai-inner' => 'oceanbase.aliyuncs.com', + 'cn-hangzhou' => 'oceanbasepro-share.cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'oceanbase.aliyuncs.com', + 'cn-shenzhen' => 'oceanbase.aliyuncs.com', + 'cn-qingdao' => 'oceanbase.aliyuncs.com', + 'cn-beijing' => 'oceanbase.aliyuncs.com', + 'cn-hangzhou-d' => 'oceanbase.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'oceanbase.aliyuncs.com', + 'ap-southeast-1' => 'oceanbasepro-share.ap-southeast-1.aliyuncs.com', + 'cn-shanghai-finance-1' => 'oceanbasepro-share.cn-shanghai-finance-1.aliyuncs.com', + ], + 'msc' => + [ + 'us-east-1' => 'msc-inner.aliyuncs.com', + 'cn-hongkong' => 'msc-inner.aliyuncs.com', + 'cn-qingdao-cm9' => 'msc-inner.aliyuncs.com', + 'cn-shanghai' => 'msc-inner.aliyuncs.com', + 'cn-shenzhen-inner' => 'msc-inner.aliyuncs.com', + 'us-west-1' => 'msc-inner.aliyuncs.com', + 'cn-shanghai-inner' => 'msc-inner.aliyuncs.com', + 'cn-hangzhou' => 'msc-inner.aliyuncs.com', + 'cn-beijing-inner' => 'msc-inner.aliyuncs.com', + 'cn-beijing' => 'msc-inner.aliyuncs.com', + 'cn-hangzhou-d' => 'msc-inner.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'msc-inner.aliyuncs.com', + 'ap-southeast-1' => 'msc-inner.aliyuncs.com', + ], + 'yundunhsm' => + [ + 'us-east-1' => 'yundunhsm.aliyuncs.com', + 'cn-hongkong' => 'yundunhsm.aliyuncs.com', + 'cn-qingdao-cm9' => 'yundunhsm.aliyuncs.com', + 'cn-shanghai' => 'yundunhsm.aliyuncs.com', + 'cn-shenzhen-inner' => 'yundunhsm.aliyuncs.com', + 'us-west-1' => 'yundunhsm.aliyuncs.com', + 'cn-shanghai-inner' => 'yundunhsm.aliyuncs.com', + 'cn-hangzhou' => 'yundunhsm.aliyuncs.com', + 'cn-beijing-inner' => 'yundunhsm.aliyuncs.com', + 'cn-beijing' => 'yundunhsm.aliyuncs.com', + 'cn-hangzhou-d' => 'yundunhsm.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'yundunhsm.aliyuncs.com', + 'ap-southeast-1' => 'yundunhsm.aliyuncs.com', + ], + 'iot' => + [ + 'cn-hongkong' => 'iot.aliyuncs.com', + 'cn-qingdao-cm9' => 'iot.aliyuncs.com', + 'cn-shanghai' => 'iot.cn-shanghai.aliyuncs.com', + 'cn-shenzhen-inner' => 'iot.aliyuncs.com', + 'us-west-1' => 'iot.us-west-1.aliyuncs.com', + 'cn-shanghai-inner' => 'iot.aliyuncs.com', + 'cn-hangzhou' => 'iot.aliyuncs.com', + 'cn-beijing-inner' => 'iot.aliyuncs.com', + 'cn-shenzhen' => 'iot.aliyuncs.com', + 'cn-qingdao' => 'iot.aliyuncs.com', + 'cn-beijing' => 'iot.aliyuncs.com', + 'cn-hangzhou-d' => 'iot.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'iot.aliyuncs.com', + 'ap-southeast-1' => 'iot.ap-southeast-1.aliyuncs.com', + 'ap-northeast-1' => 'iot.ap-northeast-1.aliyuncs.com', + 'us-east-1' => 'iot.us-east-1.aliyuncs.com', + 'eu-central-1' => 'iot.eu-central-1.aliyuncs.com', + ], + 'oms' => + [ + 'us-east-1' => 'oms.aliyuncs.com', + 'cn-hongkong' => 'oms.aliyuncs.com', + 'cn-qingdao-cm9' => 'oms.aliyuncs.com', + 'cn-shanghai' => 'oms.aliyuncs.com', + 'cn-shenzhen-inner' => 'oms.aliyuncs.com', + 'us-west-1' => 'oms.aliyuncs.com', + 'cn-shanghai-inner' => 'oms.aliyuncs.com', + 'cn-hangzhou' => 'oms.aliyuncs.com', + 'cn-beijing-inner' => 'oms.aliyuncs.com', + 'cn-shenzhen' => 'oms.aliyuncs.com', + 'cn-qingdao' => 'oms.aliyuncs.com', + 'cn-beijing' => 'oms.aliyuncs.com', + 'cn-hangzhou-d' => 'oms.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'oms.aliyuncs.com', + 'ap-southeast-1' => 'oms.aliyuncs.com', + ], + 'live' => + [ + 'us-east-1' => 'live.aliyuncs.com', + 'cn-hongkong' => 'live.aliyuncs.com', + 'cn-qingdao-cm9' => 'live.aliyuncs.com', + 'cn-shanghai' => 'live.aliyuncs.com', + 'cn-shenzhen-inner' => 'live.aliyuncs.com', + 'us-west-1' => 'live.aliyuncs.com', + 'cn-shanghai-inner' => 'live.aliyuncs.com', + 'cn-hangzhou' => 'live.aliyuncs.com', + 'cn-beijing-inner' => 'live.aliyuncs.com', + 'cn-shenzhen' => 'live.aliyuncs.com', + 'cn-qingdao' => 'live.aliyuncs.com', + 'cn-beijing' => 'live.aliyuncs.com', + 'cn-hangzhou-d' => 'live.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'live.aliyuncs.com', + 'ap-southeast-1' => 'live.aliyuncs.com', + 'ap-northeast-1' => 'live.aliyuncs.com', + 'eu-central-1' => 'live.aliyuncs.com', + 'ap-southeast-5' => 'live.aliyuncs.com', + 'ap-south-1' => 'live.aliyuncs.com', + ], + 'ubsms' => + [ + 'us-east-1' => 'ubsms.aliyuncs.com', + 'cn-hongkong' => 'ubsms.aliyuncs.com', + 'cn-qingdao-cm9' => 'ubsms.aliyuncs.com', + 'cn-shanghai' => 'ubsms.aliyuncs.com', + 'cn-shenzhen-inner' => 'ubsms.aliyuncs.com', + 'us-west-1' => 'ubsms.aliyuncs.com', + 'cn-shanghai-inner' => 'ubsms.aliyuncs.com', + 'cn-hangzhou' => 'ubsms.aliyuncs.com', + 'cn-beijing-inner' => 'ubsms.aliyuncs.com', + 'cn-shenzhen' => 'ubsms.aliyuncs.com', + 'cn-qingdao' => 'ubsms.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'ubsms.aliyuncs.com', + 'cn-hangzhou-d' => 'ubsms.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'ubsms.aliyuncs.com', + 'ap-southeast-1' => 'ubsms.aliyuncs.com', + ], + 'alert' => + [ + 'us-east-1' => 'alert.aliyuncs.com', + 'cn-hongkong' => 'alert.aliyuncs.com', + 'cn-qingdao-cm9' => 'alert.aliyuncs.com', + 'cn-shanghai' => 'alert.aliyuncs.com', + 'cn-shenzhen-inner' => 'alert.aliyuncs.com', + 'us-west-1' => 'alert.aliyuncs.com', + 'cn-shanghai-inner' => 'alert.aliyuncs.com', + 'cn-hangzhou' => 'alert.aliyuncs.com', + 'cn-beijing-inner' => 'alert.aliyuncs.com', + 'cn-shenzhen' => 'alert.aliyuncs.com', + 'cn-qingdao' => 'alert.aliyuncs.com', + 'cn-beijing' => 'alert.aliyuncs.com', + 'cn-hangzhou-d' => 'alert.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'alert.aliyuncs.com', + 'ap-southeast-1' => 'alert.aliyuncs.com', + ], + 'ace' => + [ + 'us-east-1' => 'ace.cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'ace.cn-hangzhou.aliyuncs.com', + 'cn-qingdao-cm9' => 'ace.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'ace.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-inner' => 'ace.cn-hangzhou.aliyuncs.com', + 'us-west-1' => 'ace.cn-hangzhou.aliyuncs.com', + 'cn-shanghai-inner' => 'ace.cn-hangzhou.aliyuncs.com', + 'cn-hangzhou' => 'ace.cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'ace.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'ace.cn-hangzhou.aliyuncs.com', + 'cn-qingdao' => 'ace.cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'ace.cn-hangzhou.aliyuncs.com', + 'cn-hangzhou-d' => 'ace.cn-hangzhou.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'ace.cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'ace.cn-hangzhou.aliyuncs.com', + ], + 'ams' => + [ + 'us-east-1' => 'ams.aliyuncs.com', + 'cn-hongkong' => 'ams.aliyuncs.com', + 'cn-qingdao-cm9' => 'ams.aliyuncs.com', + 'cn-shanghai' => 'ams.aliyuncs.com', + 'cn-shenzhen-inner' => 'ams.aliyuncs.com', + 'us-west-1' => 'ams.aliyuncs.com', + 'cn-shanghai-inner' => 'ams.aliyuncs.com', + 'cn-hangzhou' => 'ams.aliyuncs.com', + 'cn-beijing-inner' => 'ams.aliyuncs.com', + 'cn-beijing' => 'ams.aliyuncs.com', + 'cn-hangzhou-d' => 'ams.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'ams.aliyuncs.com', + 'ap-southeast-1' => 'ams.aliyuncs.com', + ], + 'ros' => + [ + 'us-east-1' => 'ros.aliyuncs.com', + 'cn-hongkong' => 'ros.aliyuncs.com', + 'cn-qingdao-cm9' => 'ros.aliyuncs.com', + 'cn-shanghai' => 'ros.aliyuncs.com', + 'cn-shenzhen-inner' => 'ros.aliyuncs.com', + 'us-west-1' => 'ros.aliyuncs.com', + 'cn-shanghai-inner' => 'ros.aliyuncs.com', + 'cn-hangzhou' => 'ros.aliyuncs.com', + 'cn-beijing-inner' => 'ros.aliyuncs.com', + 'cn-shenzhen' => 'ros.aliyuncs.com', + 'cn-qingdao' => 'ros.aliyuncs.com', + 'cn-beijing' => 'ros.aliyuncs.com', + 'cn-hangzhou-d' => 'ros.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'ros.aliyuncs.com', + 'ap-southeast-1' => 'ros.aliyuncs.com', + ], + 'pts' => + [ + 'us-east-1' => 'pts.aliyuncs.com', + 'cn-hongkong' => 'pts.aliyuncs.com', + 'cn-qingdao-cm9' => 'pts.aliyuncs.com', + 'cn-shanghai' => 'pts.aliyuncs.com', + 'cn-shenzhen-inner' => 'pts.aliyuncs.com', + 'us-west-1' => 'pts.aliyuncs.com', + 'cn-shanghai-inner' => 'pts.aliyuncs.com', + 'cn-hangzhou' => 'pts.aliyuncs.com', + 'cn-beijing-inner' => 'pts.aliyuncs.com', + 'cn-shenzhen' => 'pts.aliyuncs.com', + 'cn-qingdao' => 'pts.aliyuncs.com', + 'cn-beijing' => 'pts.aliyuncs.com', + 'cn-hangzhou-d' => 'pts.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'pts.aliyuncs.com', + 'ap-southeast-1' => 'pts.aliyuncs.com', + ], + 'qualitycheck' => + [ + 'us-east-1' => 'qualitycheck.aliyuncs.com', + 'cn-hongkong' => 'qualitycheck.aliyuncs.com', + 'cn-qingdao-cm9' => 'qualitycheck.aliyuncs.com', + 'cn-shanghai' => 'qualitycheck.aliyuncs.com', + 'cn-shenzhen-inner' => 'qualitycheck.aliyuncs.com', + 'us-west-1' => 'qualitycheck.aliyuncs.com', + 'cn-shanghai-inner' => 'qualitycheck.aliyuncs.com', + 'cn-hangzhou' => 'qualitycheck.aliyuncs.com', + 'cn-beijing-inner' => 'qualitycheck.aliyuncs.com', + 'cn-hangzhou-d' => 'qualitycheck.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'qualitycheck.aliyuncs.com', + 'ap-southeast-1' => 'qualitycheck.aliyuncs.com', + ], + 'm-kvstore' => + [ + 'us-east-1' => 'm-kvstore.aliyuncs.com', + 'cn-hongkong' => 'm-kvstore.aliyuncs.com', + 'cn-qingdao-cm9' => 'm-kvstore.aliyuncs.com', + 'cn-shanghai' => 'm-kvstore.aliyuncs.com', + 'cn-shenzhen-inner' => 'm-kvstore.aliyuncs.com', + 'us-west-1' => 'm-kvstore.aliyuncs.com', + 'cn-shanghai-inner' => 'm-kvstore.aliyuncs.com', + 'cn-hangzhou' => 'm-kvstore.aliyuncs.com', + 'cn-beijing-inner' => 'm-kvstore.aliyuncs.com', + 'cn-shenzhen' => 'm-kvstore.aliyuncs.com', + 'cn-qingdao' => 'm-kvstore.aliyuncs.com', + 'cn-beijing' => 'm-kvstore.aliyuncs.com', + 'cn-hangzhou-d' => 'm-kvstore.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'm-kvstore.aliyuncs.com', + 'ap-southeast-1' => 'm-kvstore.aliyuncs.com', + ], + 'highddos' => + [ + 'us-east-1' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'cn-qingdao-cm9' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-inner' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'us-west-1' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-inner' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'cn-qingdao' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou-d' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'yd-highddos-cn-hangzhou.aliyuncs.com', + ], + 'cmssitemonitor' => + [ + 'us-east-1' => 'sitemonitor.aliyuncs.com', + 'cn-hongkong' => 'sitemonitor.aliyuncs.com', + 'cn-qingdao-cm9' => 'sitemonitor.aliyuncs.com', + 'cn-shanghai' => 'sitemonitor.aliyuncs.com', + 'cn-shenzhen-inner' => 'sitemonitor.aliyuncs.com', + 'us-west-1' => 'sitemonitor.aliyuncs.com', + 'cn-shanghai-inner' => 'sitemonitor.aliyuncs.com', + 'cn-hangzhou' => 'sitemonitor.aliyuncs.com', + 'cn-beijing-inner' => 'sitemonitor.aliyuncs.com', + 'cn-shenzhen' => 'sitemonitor.aliyuncs.com', + 'cn-qingdao' => 'sitemonitor.aliyuncs.com', + 'cn-beijing' => 'sitemonitor.aliyuncs.com', + 'cn-hangzhou-d' => 'sitemonitor.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'sitemonitor.aliyuncs.com', + 'ap-southeast-1' => 'sitemonitor.aliyuncs.com', + ], + 'batchcompute' => + [ + 'us-east-1' => 'batchCompute.us-east-1.aliyuncs.com', + 'cn-hongkong' => 'batchCompute.cn-hongkong.aliyuncs.com', + 'cn-shanghai' => 'batchCompute.cn-shanghai.aliyuncs.com', + 'us-west-1' => 'batchCompute.us-west-1.aliyuncs.com', + 'cn-hangzhou' => 'batchCompute.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'batchcompute.cn-shenzhen.aliyuncs.com', + 'cn-qingdao' => 'batchcompute.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'batchCompute.cn-beijing.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'batchCompute.cn-shanghai-et2-b01.aliyuncs.com', + 'ap-southeast-1' => 'batchCompute.ap-southeast-1.aliyuncs.com', + ], + 'cf' => + [ + 'us-east-1' => 'cf.aliyuncs.com', + 'cn-hongkong' => 'cf.aliyuncs.com', + 'cn-qingdao-cm9' => 'cf.aliyuncs.com', + 'cn-shanghai' => 'cf.aliyuncs.com', + 'cn-shenzhen-inner' => 'cf.aliyuncs.com', + 'us-west-1' => 'cf.aliyuncs.com', + 'cn-shanghai-inner' => 'cf.aliyuncs.com', + 'cn-hangzhou' => 'cf.aliyuncs.com', + 'cn-beijing-inner' => 'cf.aliyuncs.com', + 'cn-shenzhen' => 'cf.aliyuncs.com', + 'cn-qingdao' => 'cf.aliyuncs.com', + 'cn-beijing' => 'cf.aliyuncs.com', + 'cn-hangzhou-d' => 'cf.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'cf.aliyuncs.com', + 'ap-southeast-1' => 'cf.aliyuncs.com', + ], + 'drds' => + [ + 'us-east-1' => 'drds.aliyuncs.com', + 'cn-hongkong' => 'drds.aliyuncs.com', + 'cn-qingdao-cm9' => 'drds.aliyuncs.com', + 'cn-shanghai' => 'drds.aliyuncs.com', + 'cn-shenzhen-inner' => 'drds.aliyuncs.com', + 'us-west-1' => 'drds.aliyuncs.com', + 'cn-shanghai-inner' => 'drds.aliyuncs.com', + 'cn-hangzhou' => 'drds.cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'drds.aliyuncs.com', + 'cn-shenzhen' => 'drds.aliyuncs.com', + 'cn-qingdao' => 'drds.aliyuncs.com', + 'cn-beijing' => 'drds.aliyuncs.com', + 'cn-hangzhou-d' => 'drds.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'drds.aliyuncs.com', + 'ap-southeast-1' => 'drds.aliyuncs.com', + ], + 'acs' => + [ + 'us-east-1' => 'acs.aliyun-inc.com', + 'cn-hongkong' => 'acs.aliyun-inc.com', + 'cn-shanghai' => 'acs.aliyun-inc.com', + 'us-west-1' => 'acs.aliyun-inc.com', + 'cn-hangzhou' => 'acs.aliyun-inc.com', + 'cn-shenzhen' => 'acs.aliyun-inc.com', + 'cn-qingdao' => 'acs.aliyun-inc.com', + 'cn-beijing' => 'acs.aliyun-inc.com', + 'cn-shanghai-et2-b01' => 'acs.aliyun-inc.com', + ], + 'httpdns' => + [ + 'us-east-1' => 'httpdns-api.aliyuncs.com', + 'cn-hongkong' => 'httpdns-api.aliyuncs.com', + 'cn-qingdao-cm9' => 'httpdns-api.aliyuncs.com', + 'cn-shanghai' => 'httpdns-api.aliyuncs.com', + 'cn-shenzhen-inner' => 'httpdns-api.aliyuncs.com', + 'us-west-1' => 'httpdns-api.aliyuncs.com', + 'cn-shanghai-inner' => 'httpdns-api.aliyuncs.com', + 'cn-hangzhou' => 'httpdns-api.aliyuncs.com', + 'cn-beijing-inner' => 'httpdns-api.aliyuncs.com', + 'cn-shenzhen' => 'httpdns-api.aliyuncs.com', + 'cn-qingdao' => 'httpdns-api.aliyuncs.com', + 'cn-beijing' => 'httpdns-api.aliyuncs.com', + 'cn-hangzhou-d' => 'httpdns-api.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'httpdns-api.aliyuncs.com', + 'ap-southeast-1' => 'httpdns-api.aliyuncs.com', + ], + 'location-inner' => + [ + 'us-east-1' => 'location-inner.aliyuncs.com', + 'cn-hongkong' => 'location-inner.aliyuncs.com', + 'cn-qingdao-cm9' => 'location-inner.aliyuncs.com', + 'cn-shanghai' => 'location-inner.aliyuncs.com', + 'cn-shenzhen-inner' => 'location-inner.aliyuncs.com', + 'us-west-1' => 'location-inner.aliyuncs.com', + 'cn-shanghai-inner' => 'location-inner.aliyuncs.com', + 'cn-hangzhou' => 'location-inner.aliyuncs.com', + 'cn-beijing-inner' => 'location-inner.aliyuncs.com', + 'cn-shenzhen' => 'location-inner.aliyuncs.com', + 'cn-qingdao' => 'location-inner.aliyuncs.com', + 'cn-beijing' => 'location-inner.aliyuncs.com', + 'cn-hangzhou-d' => 'location-inner.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'location-inner.aliyuncs.com', + 'ap-southeast-1' => 'location-inner.aliyuncs.com', + ], + 'aas' => + [ + 'us-east-1' => 'aas.aliyuncs.com', + 'cn-hongkong' => 'aas.aliyuncs.com', + 'cn-qingdao-cm9' => 'aas.aliyuncs.com', + 'cn-shanghai' => 'aas.aliyuncs.com', + 'cn-shenzhen-inner' => 'aas.aliyuncs.com', + 'us-west-1' => 'aas.aliyuncs.com', + 'cn-shanghai-inner' => 'aas.aliyuncs.com', + 'cn-hangzhou' => 'aas.aliyuncs.com', + 'cn-beijing-inner' => 'aas.aliyuncs.com', + 'cn-shenzhen' => 'aas.aliyuncs.com', + 'cn-qingdao' => 'aas.aliyuncs.com', + 'cn-beijing' => 'aas.aliyuncs.com', + 'cn-hangzhou-d' => 'aas.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'aas.aliyuncs.com', + 'ap-southeast-1' => 'aas.aliyuncs.com', + ], + 'sts' => + [ + 'cn-hangzhou' => 'sts.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'sts.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'sts.cn-shenzhen.aliyuncs.com', + 'cn-qingdao' => 'sts.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'sts.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'sts.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'sts.cn-huhehaote.aliyuncs.com', + 'cn-hongkong' => 'sts.cn-hongkong.aliyuncs.com', + 'cn-chengdu' => 'sts.cn-chengdu.aliyuncs.com', + 'ap-southeast-1' => 'sts.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'sts.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'sts.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'sts.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'sts.ap-northeast-1.aliyuncs.com', + 'ap-south-1' => 'sts.ap-south-1.aliyuncs.com', + 'us-west-1' => 'sts.us-west-1.aliyuncs.com', + 'us-east-1' => 'sts.us-east-1.aliyuncs.com', + 'eu-central-1' => 'sts.eu-central-1.aliyuncs.com', + 'me-east-1' => 'sts.me-east-1.aliyuncs.com', + 'eu-west-1' => 'sts.eu-west-1.aliyuncs.com', + ], + 'dts' => + [ + 'us-east-1' => 'dts.aliyuncs.com', + 'cn-hongkong' => 'dts.aliyuncs.com', + 'cn-qingdao-cm9' => 'dts.aliyuncs.com', + 'cn-shanghai' => 'dts.aliyuncs.com', + 'cn-shenzhen-inner' => 'dts.aliyuncs.com', + 'us-west-1' => 'dts.aliyuncs.com', + 'cn-shanghai-inner' => 'dts.aliyuncs.com', + 'cn-hangzhou' => 'dts.aliyuncs.com', + 'cn-beijing-inner' => 'dts.aliyuncs.com', + 'cn-shenzhen' => 'dts.aliyuncs.com', + 'cn-qingdao' => 'dts.aliyuncs.com', + 'cn-beijing' => 'dts.aliyuncs.com', + 'cn-hangzhou-d' => 'dts.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'dts.aliyuncs.com', + 'ap-southeast-1' => 'dts.aliyuncs.com', + 'cn-zhangjiakou' => 'dts.aliyuncs.com', + 'cn-huhehaote' => 'dts.aliyuncs.com', + 'ap-southeast-2' => 'dts.aliyuncs.com', + 'ap-southeast-3' => 'dts.aliyuncs.com', + 'ap-southeast-5' => 'dts.aliyuncs.com', + 'eu-west-1' => 'dts.aliyuncs.com', + 'eu-central-1' => 'dts.aliyuncs.com', + 'me-east-1' => 'dts.aliyuncs.com', + 'ap-south-1' => 'dts.aliyuncs.com', + 'cn-hangzhou-finance' => 'dts.aliyuncs.com', + 'cn-shanghai-finance-1' => 'dts.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'dts.aliyuncs.com', + 'cn-north-2-gov-1' => 'dts.aliyuncs.com', + ], + 'drc' => + [ + 'us-east-1' => 'drc.aliyuncs.com', + 'cn-hongkong' => 'drc.aliyuncs.com', + 'cn-qingdao-cm9' => 'drc.aliyuncs.com', + 'cn-shanghai' => 'drc.aliyuncs.com', + 'cn-shenzhen-inner' => 'drc.aliyuncs.com', + 'us-west-1' => 'drc.aliyuncs.com', + 'cn-shanghai-inner' => 'drc.aliyuncs.com', + 'cn-hangzhou' => 'drc.aliyuncs.com', + 'cn-beijing-inner' => 'drc.aliyuncs.com', + 'cn-shenzhen' => 'drc.aliyuncs.com', + 'cn-qingdao' => 'drc.aliyuncs.com', + 'cn-beijing' => 'drc.aliyuncs.com', + 'cn-hangzhou-d' => 'drc.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'drc.aliyuncs.com', + 'ap-southeast-1' => 'drc.aliyuncs.com', + ], + 'vpc-inner' => + [ + 'us-east-1' => 'vpc-inner.aliyuncs.com', + 'cn-hongkong' => 'vpc-inner.aliyuncs.com', + 'cn-shanghai' => 'vpc-inner.aliyuncs.com', + 'us-west-1' => 'vpc-inner.aliyuncs.com', + 'cn-hangzhou' => 'vpc-inner.aliyuncs.com', + 'cn-shenzhen' => 'vpc-inner.aliyuncs.com', + 'cn-qingdao' => 'vpc-inner.aliyuncs.com', + 'cn-beijing' => 'vpc-inner.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'vpc-inner.aliyuncs.com', + ], + 'crm' => + [ + 'us-east-1' => 'crm-cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'crm-cn-hangzhou.aliyuncs.com', + 'cn-qingdao-cm9' => 'crm-cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'crm-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-inner' => 'crm-cn-hangzhou.aliyuncs.com', + 'us-west-1' => 'crm-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-inner' => 'crm-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou' => 'crm-cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'crm-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'crm-cn-hangzhou.aliyuncs.com', + 'cn-qingdao' => 'crm-cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'crm-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou-d' => 'crm-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'crm-cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'crm-cn-hangzhou.aliyuncs.com', + ], + 'domain' => + [ + 'us-east-1' => 'domain.aliyuncs.com', + 'cn-hongkong' => 'domain.aliyuncs.com', + 'cn-qingdao-cm9' => 'domain.aliyuncs.com', + 'cn-shanghai' => 'domain.aliyuncs.com', + 'cn-shenzhen-inner' => 'domain.aliyuncs.com', + 'us-west-1' => 'domain.aliyuncs.com', + 'cn-shanghai-inner' => 'domain.aliyuncs.com', + 'cn-hangzhou' => 'domain.aliyuncs.com', + 'cn-beijing-inner' => 'domain.aliyuncs.com', + 'cn-shenzhen' => 'domain.aliyuncs.com', + 'cn-qingdao' => 'domain.aliyuncs.com', + 'cn-beijing' => 'domain.aliyuncs.com', + 'cn-hangzhou-d' => 'domain.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'domain.aliyuncs.com', + 'ap-southeast-1' => 'domain.aliyuncs.com', + ], + 'ots' => + [ + 'us-east-1' => 'ots.us-east-1.aliyuncs.com', + 'cn-hongkong' => 'ots.cn-hongkong.aliyuncs.com', + 'cn-qingdao-cm9' => 'ots-pop.aliyuncs.com', + 'cn-shanghai' => 'ots.cn-shanghai.aliyuncs.com', + 'cn-shenzhen-inner' => 'ots-pop.aliyuncs.com', + 'us-west-1' => 'ots.us-west-1.aliyuncs.com', + 'cn-shanghai-inner' => 'ots-pop.aliyuncs.com', + 'cn-hangzhou' => 'ots.cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'ots-pop.aliyuncs.com', + 'cn-shenzhen' => 'ots.cn-shenzhen.aliyuncs.com', + 'cn-qingdao' => 'ots.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'ots.cn-beijing.aliyuncs.com', + 'cn-hangzhou-d' => 'ots-pop.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'ots-pop.aliyuncs.com', + 'ap-southeast-1' => 'ots.ap-southeast-1.aliyuncs.com', + 'cn-zhangjiakou' => 'ots.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'ots.cn-huhehaote.aliyuncs.com', + 'ap-southeast-2' => 'ots.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'ots.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'ots.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'ots.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'ots.eu-west-1.aliyuncs.com', + 'eu-central-1' => 'ots.eu-central-1.aliyuncs.com', + 'me-east-1' => 'ots.me-east-1.aliyuncs.com', + 'ap-south-1' => 'ots.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'ots.cn-chengdu.aliyuncs.com', + 'cn-shanghai-finance-1' => 'ots.cn-shanghai-finance-1.aliyuncs.com', + ], + 'oss' => + [ + 'us-east-1' => 'oss-cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'oss-cn-hongkong.aliyuncs.com', + 'cn-qingdao-cm9' => 'oss-cn-hangzhou.aliyuncs.com', + 'cn-qingdao-finance' => 'oss-cn-qdjbp-a.aliyuncs.com', + 'cn-beijing-gov-1' => 'oss-cn-haidian-a.aliyuncs.com', + 'cn-shanghai' => 'oss-cn-shanghai.aliyuncs.com', + 'cn-shenzhen-inner' => 'oss-cn-hangzhou.aliyuncs.com', + 'us-west-1' => 'oss-us-west-1.aliyuncs.com', + 'cn-shanghai-inner' => 'oss-cn-hangzhou.aliyuncs.com', + 'cn-hangzhou-finance' => 'oss-cn-hzjbp-b-console.aliyuncs.com', + 'cn-hangzhou' => 'oss-cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'oss-cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'oss-cn-shenzhen.aliyuncs.com', + 'cn-qingdao' => 'oss-cn-qingdao.aliyuncs.com', + 'oss-cn-bjzwy' => 'oss-cn-bjzwy.aliyuncs.com', + 'cn-beijing' => 'oss-cn-beijing.aliyuncs.com', + 'cn-hangzhou-d' => 'oss-cn-hangzhou.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'oss-cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'oss-ap-southeast-1.aliyuncs.com', + ], + 'ram' => + [ + 'global' => 'ram.aliyuncs.com', + 'us-east-1' => 'ram.aliyuncs.com', + 'cn-hongkong' => 'ram.aliyuncs.com', + 'cn-qingdao-cm9' => 'ram.aliyuncs.com', + 'cn-shanghai' => 'ram.aliyuncs.com', + 'cn-shenzhen-inner' => 'ram.aliyuncs.com', + 'us-west-1' => 'ram.aliyuncs.com', + 'cn-shanghai-inner' => 'ram.aliyuncs.com', + 'cn-hangzhou' => 'ram.aliyuncs.com', + 'cn-beijing-inner' => 'ram.aliyuncs.com', + 'cn-shenzhen' => 'ram.aliyuncs.com', + 'cn-qingdao' => 'ram.aliyuncs.com', + 'cn-beijing' => 'ram.aliyuncs.com', + 'cn-hangzhou-d' => 'ram.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'ram.aliyuncs.com', + 'ap-southeast-1' => 'ram.aliyuncs.com', + ], + 'sales' => + [ + 'us-east-1' => 'sales.cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'sales.cn-hangzhou.aliyuncs.com', + 'cn-qingdao-cm9' => 'sales.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'sales.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-inner' => 'sales.cn-hangzhou.aliyuncs.com', + 'us-west-1' => 'sales.cn-hangzhou.aliyuncs.com', + 'cn-shanghai-inner' => 'sales.cn-hangzhou.aliyuncs.com', + 'cn-hangzhou' => 'sales.cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'sales.cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'sales.cn-hangzhou.aliyuncs.com', + 'cn-hangzhou-d' => 'sales.cn-hangzhou.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'sales.cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'sales.cn-hangzhou.aliyuncs.com', + ], + 'ossadmin' => + [ + 'us-east-1' => 'oss-admin.aliyuncs.com', + 'cn-hongkong' => 'oss-admin.aliyuncs.com', + 'cn-qingdao-cm9' => 'oss-admin.aliyuncs.com', + 'cn-shanghai' => 'oss-admin.aliyuncs.com', + 'cn-shenzhen-inner' => 'oss-admin.aliyuncs.com', + 'us-west-1' => 'oss-admin.aliyuncs.com', + 'cn-shanghai-inner' => 'oss-admin.aliyuncs.com', + 'cn-hangzhou' => 'oss-admin.aliyuncs.com', + 'cn-beijing-inner' => 'oss-admin.aliyuncs.com', + 'cn-shenzhen' => 'oss-admin.aliyuncs.com', + 'cn-qingdao' => 'oss-admin.aliyuncs.com', + 'cn-beijing' => 'oss-admin.aliyuncs.com', + 'cn-hangzhou-d' => 'oss-admin.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'oss-admin.aliyuncs.com', + 'ap-southeast-1' => 'oss-admin.aliyuncs.com', + ], + 'alidns' => + [ + 'us-east-1' => 'alidns.aliyuncs.com', + 'cn-hongkong' => 'alidns.aliyuncs.com', + 'cn-qingdao-cm9' => 'alidns.aliyuncs.com', + 'cn-shanghai' => 'alidns.aliyuncs.com', + 'cn-shenzhen-inner' => 'alidns.aliyuncs.com', + 'us-west-1' => 'alidns.aliyuncs.com', + 'cn-shanghai-inner' => 'alidns.aliyuncs.com', + 'cn-hangzhou' => 'alidns.aliyuncs.com', + 'cn-beijing-inner' => 'alidns.aliyuncs.com', + 'cn-shenzhen' => 'alidns.aliyuncs.com', + 'cn-qingdao' => 'alidns.aliyuncs.com', + 'cn-beijing' => 'alidns.aliyuncs.com', + 'cn-hangzhou-d' => 'alidns.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'alidns.aliyuncs.com', + 'ap-southeast-1' => 'alidns.aliyuncs.com', + ], + 'ons' => + [ + 'us-east-1' => 'ons.us-east-1.aliyuncs.com', + 'cn-hongkong' => 'ons.cn-hongkong.aliyuncs.com', + 'cn-qingdao-cm9' => 'ons.aliyuncs.com', + 'cn-shanghai' => 'ons.cn-shanghai.aliyuncs.com', + 'cn-shenzhen-inner' => 'ons.aliyuncs.com', + 'us-west-1' => 'ons.us-west-1.aliyuncs.com', + 'cn-shanghai-inner' => 'ons.aliyuncs.com', + 'cn-hangzhou' => 'ons.cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'ons.aliyuncs.com', + 'cn-shenzhen' => 'ons.cn-shenzhen.aliyuncs.com', + 'cn-qingdao' => 'ons.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'ons.cn-beijing.aliyuncs.com', + 'cn-hangzhou-d' => 'ons.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'ons.aliyuncs.com', + 'ap-southeast-1' => 'ons.ap-southeast-1.aliyuncs.com', + 'cn-zhangjiakou' => 'ons.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'ons.cn-huhehaote.aliyuncs.com', + 'ap-southeast-2' => 'ons.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'ons.ap-southeast-3.aliyuncs.com', + 'ap-northeast-1' => 'ons.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'ons.eu-west-1.aliyuncs.com', + 'eu-central-1' => 'ons.eu-central-1.aliyuncs.com', + 'me-east-1' => 'ons.me-east-1.aliyuncs.com', + 'ap-south-1' => 'ons.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'ons.cn-chengdu.aliyuncs.com', + 'cn-hangzhou-finance' => 'ons.cn-hangzhou-finance.aliyuncs.com', + 'cn-shanghai-finance-1' => 'ons.cn-shanghai-finance-1.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'ons.cn-shenzhen-finance-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'ons.cn-north-2-gov-1.aliyuncs.com', + 'ap-southeast-5' => 'ons.ap-southeast-5.aliyuncs.com', + ], + 'cdn' => + [ + 'global' => 'cdn.aliyuncs.com', + 'us-east-1' => 'cdn.aliyuncs.com', + 'cn-hongkong' => 'cdn.aliyuncs.com', + 'cn-qingdao-cm9' => 'cdn.aliyuncs.com', + 'cn-shanghai' => 'cdn.aliyuncs.com', + 'cn-shenzhen-inner' => 'cdn.aliyuncs.com', + 'us-west-1' => 'cdn.aliyuncs.com', + 'cn-shanghai-inner' => 'cdn.aliyuncs.com', + 'cn-hangzhou' => 'cdn.aliyuncs.com', + 'cn-beijing-inner' => 'cdn.aliyuncs.com', + 'cn-shenzhen' => 'cdn.aliyuncs.com', + 'cn-qingdao' => 'cdn.aliyuncs.com', + 'cn-beijing' => 'cdn.aliyuncs.com', + 'cn-hangzhou-d' => 'cdn.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'cdn.aliyuncs.com', + 'ap-southeast-1' => 'cdn.aliyuncs.com', + ], + 'yundunddos' => + [ + 'us-east-1' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + 'cn-qingdao-cm9' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-inner' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + 'us-west-1' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + 'cn-shanghai-inner' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + 'cn-hangzhou' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + 'cn-hangzhou-d' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'inner-yundun-ddos.cn-hangzhou.aliyuncs.com', + ], + 'kvstore' => + [ + 'ap-northeast-1' => 'r-kvstore.ap-northeast-1.aliyuncs.com', + ], + 'cloudapi' => + [ + 'cn-hongkong' => 'apigateway.cn-hongkong.aliyuncs.com', + 'cn-shanghai' => 'apigateway.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'apigateway.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'apigateway.cn-shenzhen.aliyuncs.com', + 'cn-qingdao' => 'apigateway.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'apigateway.cn-beijing.aliyuncs.com', + 'ap-southeast-1' => 'apigateway.ap-southeast-1.aliyuncs.com', + ], + 'mts' => + [ + 'cn-hongkong' => 'mts.cn-hongkong.aliyuncs.com', + 'cn-qingdao-cm9' => 'mts.cn-qingdao.aliyuncs.com', + 'cn-shanghai' => 'mts.cn-shanghai.aliyuncs.com', + 'cn-shenzhen-inner' => 'mts.cn-shenzhen.aliyuncs.com', + 'us-west-1' => 'mts.us-west-1.aliyuncs.com', + 'cn-shanghai-inner' => 'mts.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'mts.cn-hangzhou.aliyuncs.com', + 'cn-beijing-inner' => 'mts.cn-beijing.aliyuncs.com', + 'cn-shenzhen' => 'mts.cn-shenzhen.aliyuncs.com', + 'cn-qingdao' => 'mts.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'mts.cn-beijing.aliyuncs.com', + 'cn-hangzhou-d' => 'mts.cn-hangzhou.aliyuncs.com', + 'cn-shanghai-et2-b01' => 'mts.cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'mts.ap-southeast-1.aliyuncs.com', + 'cn-zhangjiakou' => 'mts.cn-zhangjiakou.aliyuncs.com', + 'ap-northeast-1' => 'mts.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'mts.eu-west-1.aliyuncs.com', + 'eu-central-1' => 'mts.eu-central-1.aliyuncs.com', + 'ap-south-1' => 'mts.ap-south-1.aliyuncs.com', + 'ap-southeast-5' => 'mts.ap-southeast-5.aliyuncs.com', + ], + 'saf' => + [ + 'cn-shanghai' => 'saf.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'saf.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'saf.cn-shenzhen.aliyuncs.com', + 'ap-southeast-1' => 'saf.ap-southeast-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'saf.cn-north-2-gov-1.aliyuncs.com', + 'cn-zhangjiakou' => 'saf.cn-zhangjiakou.aliyuncs.com', + ], + 'arms' => + [ + 'cn-shanghai' => 'arms.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'arms.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'arms.cn-shenzhen.aliyuncs.com', + 'cn-beijing' => 'arms.cn-beijing.aliyuncs.com', + 'cn-qingdao' => 'arms.cn-qingdao.aliyuncs.com', + 'cn-zhangjiakou' => 'arms.cn-zhangjiakou.aliyuncs.com', + 'cn-hongkong' => 'arms.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'arms.ap-southeast-1.aliyuncs.com', + 'ap-south-1' => 'arms.ap-south-1.aliyuncs.com', + ], + 'apigateway' => + [ + 'cn-shanghai' => 'apigateway.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'apigateway.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'apigateway.cn-shenzhen.aliyuncs.com', + 'cn-qingdao' => 'apigateway.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'apigateway.cn-beijing.aliyuncs.com', + 'ap-southeast-1' => 'apigateway.ap-southeast-1.aliyuncs.com', + 'cn-hongkong' => 'apigateway.cn-hongkong.aliyuncs.com', + 'ap-southeast-2' => 'apigateway.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'apigateway.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'apigateway.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'apigateway.ap-northeast-1.aliyuncs.com', + 'eu-central-1' => 'apigateway.eu-central-1.aliyuncs.com', + 'ap-south-1' => 'apigateway.ap-south-1.aliyuncs.com', + 'eu-west-1' => 'apigateway.eu-west-1.aliyuncs.com', + 'me-east-1' => 'apigateway.me-east-1.aliyuncs.com', + 'us-east-1' => 'apigateway.us-east-1.aliyuncs.com', + 'us-west-1' => 'apigateway.us-west-1.aliyuncs.com', + 'cn-zhangjiakou' => 'apigateway.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'apigateway.cn-huhehaote.aliyuncs.com', + 'cn-chengdu' => 'apigateway.cn-chengdu.aliyuncs.com', + 'cn-north-2-gov-1' => 'apigateway.cn-north-2-gov-1.aliyuncs.com', + ], + 'vod' => + [ + 'cn-shanghai' => 'vod.cn-shanghai.aliyuncs.com', + 'cn-beijing' => 'vod.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'vod.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'vod.cn-shanghai.aliyuncs.com', + 'ap-southeast-1' => 'vod.ap-southeast-1.aliyuncs.com', + 'eu-central-1' => 'vod.eu-central-1.aliyuncs.com', + 'cn-zhangjiakou' => 'vod.cn-zhangjiakou.aliyuncs.com', + 'cn-hongkong' => 'vod.cn-hongkong.aliyuncs.com', + 'ap-southeast-5' => 'vod.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'vod.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'vod.eu-west-1.aliyuncs.com', + 'us-west-1' => 'vod.us-west-1.aliyuncs.com', + 'ap-south-1' => 'vod.ap-south-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'vod.cn-north-2-gov-1.aliyuncs.com', + ], + 'afs' => + [ + 'cn-hangzhou' => 'afs.aliyuncs.com', + ], + 'oas' => + [ + 'cn-hangzhou' => 'cn-hangzhou.oas.aliyuncs.com', + 'cn-shenzhen' => 'cn-shenzhen.oas.aliyuncs.com', + 'cn-beijing' => 'cn-beijing.oas.aliyuncs.com', + ], + 'alikafka' => + [ + 'cn-qingdao' => 'alikafka.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'alikafka.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'alikafka.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'alikafka.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'alikafka.cn-shenzhen.aliyuncs.com', + 'cn-zhangjiakou' => 'alikafka.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'alikafka.cn-huhehaote.aliyuncs.com', + 'cn-hongkong' => 'alikafka.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'alikafka.ap-southeast-1.aliyuncs.com', + 'ap-southeast-5' => 'alikafka.ap-southeast-5.aliyuncs.com', + 'ap-south-1' => 'alikafka.ap-south-1.aliyuncs.com', + 'cn-hangzhou-finance' => 'alikafka.cn-hangzhou-finance.aliyuncs.com', + 'cn-shanghai-finance-1' => 'alikafka.cn-shanghai-finance-1.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'alikafka.cn-shenzhen-finance-1.aliyuncs.com', + ], + 'cbn' => + [ + 'cn-qingdao' => 'cbn.aliyuncs.com', + 'cn-beijing' => 'cbn.aliyuncs.com', + 'cn-zhangjiakou' => 'cbn.aliyuncs.com', + 'cn-huhehaote' => 'cbn.aliyuncs.com', + 'cn-hangzhou' => 'cbn.aliyuncs.com', + 'cn-shanghai' => 'cbn.aliyuncs.com', + 'cn-shenzhen' => 'cbn.aliyuncs.com', + 'cn-hongkong' => 'cbn.aliyuncs.com', + 'ap-southeast-1' => 'cbn.aliyuncs.com', + 'ap-southeast-2' => 'cbn.aliyuncs.com', + 'ap-southeast-3' => 'cbn.aliyuncs.com', + 'ap-southeast-5' => 'cbn.aliyuncs.com', + 'ap-northeast-1' => 'cbn.aliyuncs.com', + 'eu-west-1' => 'cbn.aliyuncs.com', + 'us-west-1' => 'cbn.aliyuncs.com', + 'us-east-1' => 'cbn.aliyuncs.com', + 'eu-central-1' => 'cbn.aliyuncs.com', + 'me-east-1' => 'cbn.aliyuncs.com', + 'ap-south-1' => 'cbn.aliyuncs.com', + 'cn-chengdu' => 'cbn.aliyuncs.com', + 'cn-shanghai-finance-1' => 'cbn.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'cbn.aliyuncs.com', + ], + 'onsvip' => + [ + 'cn-qingdao' => 'ons.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'ons.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'ons.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'ons.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'ons.cn-shenzhen.aliyuncs.com', + 'ap-southeast-1' => 'ons.ap-southeast-1.aliyuncs.com', + 'cn-hangzhou-finance' => 'ons.cn-hangzhou-finance.aliyuncs.com', + 'cn-shanghai-finance-1' => 'ons.cn-shanghai-finance.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'ons.cn-shenzhen-finance.aliyuncs.com', + ], + 'ddosbgp' => + [ + 'cn-qingdao' => 'ddosbgp.aliyuncs.com', + 'cn-beijing' => 'ddosbgp.aliyuncs.com', + 'cn-zhangjiakou' => 'ddosbgp.aliyuncs.com', + 'cn-huhehaote' => 'ddosbgp.aliyuncs.com', + 'cn-hangzhou' => 'ddosbgp.aliyuncs.com', + 'cn-shanghai' => 'ddosbgp.aliyuncs.com', + 'cn-shenzhen' => 'ddosbgp.aliyuncs.com', + 'cn-hongkong' => 'ddosbgp.cn-hongkong.aliyuncs.com', + 'us-west-1' => 'ddosbgp.us-west-1.aliyuncs.com', + 'ap-southeast-1' => 'ddosbgp.ap-southeast-1.aliyuncs.com', + 'us-east-1' => 'ddosbgp.us-east-1.aliyuncs.com', + ], + 'ehs' => + [ + 'cn-qingdao' => 'ehpc.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'ehpc.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'ehpc.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'ehpc.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'ehpc.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'ehpc.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'ehpc.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'ehpc.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'ehpc.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'ehpc.ap-southeast-2.aliyuncs.com', + 'eu-central-1' => 'ehpc.eu-central-1.aliyuncs.com', + 'ap-northeast-1' => 'ehpc.ap-northeast-1.aliyuncs.com', + ], + 'redisa' => + [ + 'cn-qingdao' => 'r-kvstore.aliyuncs.com', + 'cn-beijing' => 'r-kvstore.aliyuncs.com', + 'cn-zhangjiakou' => 'r-kvstore.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'r-kvstore.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'r-kvstore.aliyuncs.com', + 'cn-shanghai' => 'r-kvstore.aliyuncs.com', + 'cn-shenzhen' => 'r-kvstore.aliyuncs.com', + 'cn-hongkong' => 'r-kvstore.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'r-kvstore.aliyuncs.com', + 'ap-southeast-2' => 'r-kvstore.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'r-kvstore.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'r-kvstore.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'r-kvstore.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'r-kvstore.eu-west-1.aliyuncs.com', + 'us-west-1' => 'r-kvstore.aliyuncs.com', + 'us-east-1' => 'r-kvstore.aliyuncs.com', + 'eu-central-1' => 'r-kvstore.eu-central-1.aliyuncs.com', + 'me-east-1' => 'r-kvstore.me-east-1.aliyuncs.com', + 'ap-south-1' => 'r-kvstore.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'r-kvstore.cn-chengdu.aliyuncs.com', + 'cn-hangzhou-finance' => 'r-kvstore.aliyuncs.com', + 'cn-shanghai-finance-1' => 'r-kvstore.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'r-kvstore.aliyuncs.com', + 'cn-north-2-gov-1' => 'r-kvstore.aliyuncs.com', + ], + 'nas' => + [ + 'cn-qingdao' => 'nas.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'nas.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'nas.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'nas.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'nas.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'nas.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'nas.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'nas.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'nas.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'nas.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'nas.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'nas.ap-southeast-5.aliyuncs.com', + 'us-east-1' => 'nas.us-east-1.aliyuncs.com', + 'eu-central-1' => 'nas.eu-central-1.aliyuncs.com', + 'ap-south-1' => 'nas.ap-south-1.aliyuncs.com', + 'ap-northeast-1' => 'nas.ap-northeast-1.aliyuncs.com', + 'us-west-1' => 'nas.us-west-1.aliyuncs.com', + 'cn-shanghai-finance-1' => 'nas.cn-shanghai-finance-1.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'nas.cn-shenzhen-finance-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'nas.cn-north-2-gov-1.aliyuncs.com', + 'eu-west-1' => 'nas.eu-west-1.aliyuncs.com', + 'cn-chengdu' => 'nas.cn-chengdu.aliyuncs.com', + ], + 'hbase' => + [ + 'cn-qingdao' => 'hbase.aliyuncs.com', + 'cn-beijing' => 'hbase.aliyuncs.com', + 'cn-huhehaote' => 'hbase.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'hbase.aliyuncs.com', + 'cn-shanghai' => 'hbase.aliyuncs.com', + 'cn-shenzhen' => 'hbase.aliyuncs.com', + 'ap-southeast-1' => 'hbase.aliyuncs.com', + 'ap-southeast-2' => 'hbase.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'hbase.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'hbase.ap-southeast-5.aliyuncs.com', + 'us-west-1' => 'hbase.aliyuncs.com', + 'us-east-1' => 'hbase.aliyuncs.com', + 'eu-central-1' => 'hbase.eu-central-1.aliyuncs.com', + 'me-east-1' => 'hbase.me-east-1.aliyuncs.com', + 'ap-south-1' => 'hbase.ap-south-1.aliyuncs.com', + 'eu-west-1' => 'hbase.eu-west-1.aliyuncs.com', + 'cn-hangzhou-finance' => 'hbase.aliyuncs.com', + 'cn-shanghai-finance-1' => 'hbase.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'hbase.aliyuncs.com', + 'cn-north-2-gov-1' => 'hbase.aliyuncs.com', + 'cn-zhangjiakou' => 'hbase.cn-zhangjiakou.aliyuncs.com', + 'cn-hongkong' => 'hbase.aliyuncs.com', + ], + 'ddosbasic' => + [ + 'cn-qingdao' => 'antiddos.aliyuncs.com', + 'cn-beijing' => 'antiddos.aliyuncs.com', + 'cn-zhangjiakou' => 'antiddos-openapi.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'antiddos-openapi.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'antiddos.aliyuncs.com', + 'cn-shanghai' => 'antiddos.aliyuncs.com', + 'cn-shenzhen' => 'antiddos.aliyuncs.com', + 'cn-hongkong' => 'antiddos.aliyuncs.com', + 'ap-southeast-1' => 'antiddos.aliyuncs.com', + 'ap-southeast-2' => 'antiddos-openapi.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'antiddos-openapi.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'antiddos-openapi.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'antiddos-openapi.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'antiddos-openapi.eu-west-1.aliyuncs.com', + 'us-west-1' => 'antiddos.aliyuncs.com', + 'us-east-1' => 'antiddos.aliyuncs.com', + 'eu-central-1' => 'antiddos-openapi.eu-central-1.aliyuncs.com', + 'me-east-1' => 'antiddos-openapi.me-east-1.aliyuncs.com', + 'ap-south-1' => 'antiddos-openapi.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'antiddos-openapi.cn-chengdu.aliyuncs.com', + 'cn-shanghai-finance-1' => 'antiddos.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'antiddos.aliyuncs.com', + 'cn-north-2-gov-1' => 'antiddos.aliyuncs.com', + ], + 'polardb' => + [ + 'cn-qingdao' => 'polardb.aliyuncs.com', + 'cn-beijing' => 'polardb.aliyuncs.com', + 'cn-huhehaote' => 'polardb.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'polardb.aliyuncs.com', + 'cn-shanghai' => 'polardb.aliyuncs.com', + 'cn-shenzhen' => 'polardb.aliyuncs.com', + 'cn-hongkong' => 'polardb.aliyuncs.com', + 'cn-zhangjiakou' => 'polardb.cn-zhangjiakou.aliyuncs.com', + 'ap-southeast-1' => 'polardb.aliyuncs.com', + 'ap-southeast-3' => 'polardb.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'polardb.ap-southeast-5.aliyuncs.com', + 'us-west-1' => 'polardb.aliyuncs.com', + 'cn-hangzhou-finance' => 'polardb.aliyuncs.com', + 'cn-shanghai-finance-1' => 'polardb.aliyuncs.com', + 'eu-central-1' => 'polardb.eu-central-1.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'polardb.aliyuncs.com', + 'ap-south-1' => 'polardb.ap-south-1.aliyuncs.com', + ], + 'actiontrail' => + [ + 'cn-qingdao' => 'actiontrail.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'actiontrail.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'actiontrail.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'actiontrail.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'actiontrail.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'actiontrail.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'actiontrail.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'actiontrail.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'actiontrail.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'actiontrail.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'actiontrail.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'actiontrail.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'actiontrail.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'actiontrail.eu-west-1.aliyuncs.com', + 'us-west-1' => 'actiontrail.us-west-1.aliyuncs.com', + 'us-east-1' => 'actiontrail.us-east-1.aliyuncs.com', + 'eu-central-1' => 'actiontrail.eu-central-1.aliyuncs.com', + 'me-east-1' => 'actiontrail.me-east-1.aliyuncs.com', + 'ap-south-1' => 'actiontrail.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'actiontrail.cn-chengdu.aliyuncs.com', + 'cn-shanghai-finance-1' => 'actiontrail.cn-shanghai-finance-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'actiontrail.cn-north-2-gov-1.aliyuncs.com', + ], + 'codepipeline' => + [ + 'cn-beijing' => 'cds.cn-beijing.aliyuncs.com', + ], + 'hcs_sgw' => + [ + 'cn-beijing' => 'sgw.cn-shanghai.aliyuncs.com', + 'cn-zhangjiakou' => 'sgw.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'sgw.cn-shanghai.aliyuncs.com', + 'cn-shanghai' => 'sgw.cn-shanghai.aliyuncs.com', + 'cn-hongkong' => 'sgw.cn-shanghai.aliyuncs.com', + 'ap-southeast-1' => 'sgw.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'sgw.ap-southeast-2.aliyuncs.com', + 'eu-central-1' => 'sgw.eu-central-1.aliyuncs.com', + 'cn-qingdao' => 'sgw.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'sgw.cn-shanghai.aliyuncs.com', + 'cn-huhehaote' => 'sgw.cn-shanghai.aliyuncs.com', + 'cn-chengdu' => 'sgw.cn-shanghai.aliyuncs.com', + 'ap-southeast-5' => 'sgw.ap-southeast-5.aliyuncs.com', + ], + 'openanalytics' => + [ + 'cn-beijing' => 'openanalytics.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'openanalytics.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'openanalytics.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'openanalytics.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'openanalytics.cn-shenzhen.aliyuncs.com', + 'ap-southeast-1' => 'openanalytics.ap-southeast-1.aliyuncs.com', + 'ap-southeast-3' => 'openanalytics.ap-southeast-3.aliyuncs.com', + 'eu-west-1' => 'openanalytics.eu-west-1.aliyuncs.com', + 'cn-hongkong' => 'openanalytics.cn-hongkong.aliyuncs.com', + 'us-west-1' => 'openanalytics.us-west-1.aliyuncs.com', + 'ap-southeast-2' => 'datalakeanalytics.ap-southeast-2.aliyuncs.com', + 'ap-northeast-1' => 'datalakeanalytics.ap-northeast-1.aliyuncs.com', + 'us-east-1' => 'datalakeanalytics.us-east-1.aliyuncs.com', + 'eu-central-1' => 'datalakeanalytics.eu-central-1.aliyuncs.com', + 'ap-south-1' => 'openanalytics.ap-south-1.aliyuncs.com', + ], + 'clouddesktop' => + [ + 'cn-beijing' => 'clouddesktop.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'clouddesktop.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'clouddesktop.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'clouddesktop.cn-shenzhen.aliyuncs.com', + ], + 'ivision' => + [ + 'cn-beijing' => 'ivision.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'ivision.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'ivision.cn-shanghai.aliyuncs.com', + ], + 'fc' => + [ + 'cn-beijing' => 'cn-beijing.fc.aliyuncs.com', + 'cn-hangzhou' => 'cn-hangzhou.fc.aliyuncs.com', + 'cn-shanghai' => 'cn-shanghai.fc.aliyuncs.com', + 'cn-shenzhen' => 'cn-shenzhen.fc.aliyuncs.com', + 'ap-southeast-2' => 'ap-southeast-2.fc.aliyuncs.com', + 'cn-huhehaote' => 'cn-huhehaote.fc.aliyuncs.com', + ], + 'hsm' => + [ + 'cn-beijing' => 'hsm.aliyuncs.com', + 'cn-hangzhou' => 'hsm.aliyuncs.com', + 'cn-shanghai' => 'hsm.aliyuncs.com', + 'cn-shenzhen' => 'hsm.aliyuncs.com', + 'cn-hongkong' => 'hsm.aliyuncs.com', + 'ap-southeast-1' => 'hsm.aliyuncs.com', + 'cn-shanghai-finance-1' => 'hsm.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'hsm.aliyuncs.com', + 'cn-hangzhou-finance' => 'hsm.aliyuncs.com', + 'cn-north-2-gov-1' => 'hsm.aliyuncs.com', + ], + 'petadata' => + [ + 'cn-beijing' => 'petadata.aliyuncs.com', + 'cn-zhangjiakou' => 'petadata.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'petadata.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'petadata.aliyuncs.com', + 'cn-shanghai' => 'petadata.aliyuncs.com', + 'cn-shenzhen' => 'petadata.aliyuncs.com', + 'ap-southeast-1' => 'petadata.aliyuncs.com', + 'ap-southeast-2' => 'petadata.ap-southeast-2.aliyuncs.com', + 'ap-southeast-5' => 'petadata.ap-southeast-5.aliyuncs.com', + 'us-west-1' => 'petadata.aliyuncs.com', + 'us-east-1' => 'petadata.aliyuncs.com', + 'eu-central-1' => 'petadata.eu-central-1.aliyuncs.com', + 'me-east-1' => 'petadata.me-east-1.aliyuncs.com', + 'cn-hongkong' => 'petadata.aliyuncs.com', + 'cn-qingdao' => 'petadata.aliyuncs.com', + ], + 'gpdb' => + [ + 'cn-beijing' => 'gpdb.aliyuncs.com', + 'cn-zhangjiakou' => 'gpdb.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'gpdb.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'gpdb.aliyuncs.com', + 'cn-shanghai' => 'gpdb.aliyuncs.com', + 'cn-shenzhen' => 'gpdb.aliyuncs.com', + 'ap-southeast-1' => 'gpdb.aliyuncs.com', + 'ap-southeast-2' => 'gpdb.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'gpdb.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'gpdb.ap-southeast-5.aliyuncs.com', + 'eu-west-1' => 'gpdb.eu-west-1.aliyuncs.com', + 'us-west-1' => 'gpdb.aliyuncs.com', + 'us-east-1' => 'gpdb.aliyuncs.com', + 'eu-central-1' => 'gpdb.eu-central-1.aliyuncs.com', + 'ap-south-1' => 'gpdb.ap-south-1.aliyuncs.com', + 'ap-northeast-1' => 'gpdb.ap-northeast-1.aliyuncs.com', + 'cn-hongkong' => 'gpdb.aliyuncs.com', + 'cn-chengdu' => 'gpdb.cn-chengdu.aliyuncs.com', + 'cn-hangzhou-finance' => 'gpdb.aliyuncs.com', + 'cn-shanghai-finance-1' => 'gpdb.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'gpdb.aliyuncs.com', + ], + 'eci' => + [ + 'cn-beijing' => 'eci.aliyuncs.com', + 'cn-hangzhou' => 'eci.aliyuncs.com', + 'cn-shanghai' => 'eci.aliyuncs.com', + 'cn-shenzhen' => 'eci.aliyuncs.com', + 'ap-southeast-1' => 'eci.aliyuncs.com', + 'us-west-1' => 'eci.aliyuncs.com', + 'cn-hongkong' => 'eci.aliyuncs.com', + 'cn-zhangjiakou' => 'eci.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'eci.cn-huhehaote.aliyuncs.com', + 'ap-southeast-2' => 'eci.ap-southeast-2.aliyuncs.com', + 'eu-west-1' => 'eci.eu-west-1.aliyuncs.com', + 'us-east-1' => 'eci.aliyuncs.com', + 'eu-central-1' => 'eci.eu-central-1.aliyuncs.com', + 'cn-chengdu' => 'eci.cn-chengdu.aliyuncs.com', + 'ap-southeast-5' => 'eci.ap-southeast-5.aliyuncs.com', + ], + 'airec' => + [ + 'cn-beijing' => 'airec.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'airec.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'airec.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'airec.cn-shenzhen.aliyuncs.com', + ], + 'imm' => + [ + 'cn-beijing' => 'imm.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'imm.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'imm.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'imm.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'imm.cn-shenzhen.aliyuncs.com', + 'ap-southeast-1' => 'imm.ap-southeast-1.aliyuncs.com', + ], + 'gameshield' => + [ + 'cn-zhangjiakou' => 'gameshield.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'gameshield.aliyuncs.com', + ], + 'ims' => + [ + 'cn-hangzhou' => 'ims.aliyuncs.com', + ], + 'cloudfirewall' => + [ + 'cn-hangzhou' => 'cloudfw.cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'cloudfw.ap-southeast-1.aliyuncs.com', + ], + 'ens' => + [ + 'cn-hangzhou' => 'ens.aliyuncs.com', + 'ap-southeast-1' => 'ens.ap-southeast-1.aliyuncs.com', + ], + 'hitsdb' => + [ + 'cn-hangzhou' => 'hitsdb.aliyuncs.com', + 'cn-qingdao' => 'hitsdb.aliyuncs.com', + 'cn-beijing' => 'hitsdb.aliyuncs.com', + 'cn-zhangjiakou' => 'hitsdb.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'hitsdb.cn-huhehaote.aliyuncs.com', + 'cn-shanghai' => 'hitsdb.aliyuncs.com', + 'cn-shenzhen' => 'hitsdb.aliyuncs.com', + 'cn-hongkong' => 'hitsdb.aliyuncs.com', + 'ap-southeast-5' => 'hitsdb.ap-southeast-5.aliyuncs.com', + 'us-west-1' => 'hitsdb.aliyuncs.com', + 'us-east-1' => 'hitsdb.aliyuncs.com', + ], + 'ddos' => + [ + 'cn-hangzhou' => 'ddospro.cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'ddospro.cn-hongkong.aliyuncs.com', + ], + 'rtc' => + [ + 'cn-hangzhou' => 'rtc.aliyuncs.com', + ], + 'emas' => + [ + 'cn-hangzhou' => 'mhub.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'mhub.cn-shanghai.aliyuncs.com', + ], + 'vipaegis' => + [ + 'cn-hangzhou' => 'aegis.cn-hangzhou.aliyuncs.com', + 'ap-southeast-3' => 'aegis.ap-southeast-3.aliyuncs.com', + ], + 'ddosrewards' => + [ + 'cn-hangzhou' => 'ddosright.cn-hangzhou.aliyuncs.com', + ], + 'cloudap' => + [ + 'cn-hangzhou' => 'cloudwf.aliyuncs.com', + ], + 'ensdisk' => + [ + 'cn-hangzhou' => 'ens.aliyuncs.com', + ], + 'bastionhost' => + [ + 'cn-hangzhou' => 'yundun-bastionhost.aliyuncs.com', + 'cn-qingdao' => 'yundun-bastionhost.aliyuncs.com', + 'cn-beijing' => 'yundun-bastionhost.aliyuncs.com', + 'cn-chengdu' => 'yundun-bastionhost.aliyuncs.com', + 'cn-zhangjiakou' => 'yundun-bastionhost.aliyuncs.com', + 'cn-huhehaote' => 'yundun-bastionhost.aliyuncs.com', + 'cn-shanghai' => 'yundun-bastionhost.aliyuncs.com', + 'cn-shenzhen' => 'yundun-bastionhost.aliyuncs.com', + 'cn-hongkong' => 'yundun-bastionhost.aliyuncs.com', + 'ap-southeast-1' => 'yundun-bastionhost.aliyuncs.com', + 'ap-southeast-2' => 'yundun-bastionhost.aliyuncs.com', + 'ap-southeast-3' => 'yundun-bastionhost.aliyuncs.com', + 'ap-southeast-5' => 'yundun-bastionhost.aliyuncs.com', + 'ap-northeast-1' => 'yundun-bastionhost.aliyuncs.com', + 'eu-west-1' => 'yundun-bastionhost.aliyuncs.com', + 'us-west-1' => 'yundun-bastionhost.aliyuncs.com', + 'us-east-1' => 'yundun-bastionhost.aliyuncs.com', + 'eu-central-1' => 'yundun-bastionhost.aliyuncs.com', + 'me-east-1' => 'yundun-bastionhost.aliyuncs.com', + 'ap-south-1' => 'yundun-bastionhost.aliyuncs.com', + 'cn-shanghai-finance-1' => 'yundun-bastionhost.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'yundun-bastionhost.aliyuncs.com', + 'cn-north-2-gov-1' => 'yundun-bastionhost.aliyuncs.com', + ], + 'pvtz' => + [ + 'cn-hangzhou' => 'pvtz.aliyuncs.com', + ], + 'ccs' => + [ + 'cn-hangzhou' => 'ccs.aliyuncs.com', + ], + 'yunmarket' => + [ + 'cn-hangzhou' => 'market.aliyuncs.com', + ], + 'cas' => + [ + 'cn-hangzhou' => 'cas.aliyuncs.com', + 'ap-southeast-2' => 'cas.ap-southeast-2.aliyuncs.com', + 'ap-northeast-1' => 'cas.ap-northeast-1.aliyuncs.com', + 'eu-central-1' => 'cas.eu-central-1.aliyuncs.com', + 'me-east-1' => 'cas.me-east-1.aliyuncs.com', + 'ap-south-1' => 'cas.ap-south-1.aliyuncs.com', + ], + 'ddoscoo' => + [ + 'cn-hangzhou' => 'ddoscoo.cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'ddoscoo.ap-southeast-1.aliyuncs.com', + ], + 'waf' => + [ + 'cn-hangzhou' => 'wafopenapi.cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'wafopenapi.ap-southeast-1.aliyuncs.com', + ], + 'xianzhi' => + [ + 'cn-hangzhou' => 'xianzhi.aliyuncs.com', + ], + 'sas' => + [ + 'cn-hangzhou' => 'tds.aliyuncs.com', + 'ap-southeast-3' => 'tds.ap-southeast-3.aliyuncs.com', + ], + 'cloudauth' => + [ + 'cn-hangzhou' => 'cloudauth.aliyuncs.com', + ], + 'dmsenterprise' => + [ + 'cn-hangzhou' => 'dms-enterprise.aliyuncs.com', + 'cn-shanghai' => 'dms-enterprise.aliyuncs.com', + 'cn-shenzhen' => 'dms-enterprise.aliyuncs.com', + 'cn-beijing' => 'dms-enterprise.aliyuncs.com', + 'cn-qingdao' => 'dms-enterprise.aliyuncs.com', + 'ap-northeast-1' => 'dms-enterprise.aliyuncs.com', + ], + 'baas' => + [ + 'cn-hangzhou' => 'baas.cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'baas.ap-southeast-1.aliyuncs.com', + 'ap-northeast-1' => 'baas.ap-southeast-1.aliyuncs.com', + 'cn-beijing' => 'baas.aliyuncs.com', + 'cn-shanghai' => 'baas.aliyuncs.com', + 'cn-shenzhen' => 'baas.aliyuncs.com', + 'cn-hongkong' => 'baas.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'baas.ap-southeast-1.aliyuncs.com', + 'us-east-1' => 'baas.ap-southeast-1.aliyuncs.com', + 'eu-central-1' => 'baas.ap-southeast-1.aliyuncs.com', + 'cn-qingdao' => 'baas.aliyuncs.com', + 'cn-zhangjiakou' => 'baas.aliyuncs.com', + 'cn-huhehaote' => 'baas.aliyuncs.com', + 'eu-west-1' => 'baas.ap-southeast-1.aliyuncs.com', + 'us-west-1' => 'baas.ap-southeast-1.aliyuncs.com', + 'ap-south-1' => 'baas.ap-southeast-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'baas.cn-north-2-gov-1.aliyuncs.com', + ], + 'alimt' => + [ + 'cn-hangzhou' => 'mt.cn-hangzhou.aliyuncs.com', + ], + 'dcdn' => + [ + 'cn-hangzhou' => 'dcdn.aliyuncs.com', + ], + 'hcs_mgw' => + [ + 'cn-hangzhou' => 'mgw.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'mgw.cn-shanghai.aliyuncs.com', + 'ap-southeast-1' => 'mgw.ap-southeast-1.aliyuncs.com', + ], + 'linkedmall' => + [ + 'cn-hangzhou' => 'linkedmall.aliyuncs.com', + 'cn-shanghai' => 'linkedmall.aliyuncs.com', + ], + 'cps' => + [ + 'cn-hangzhou' => 'cloudpush.aliyuncs.com', + ], + 'scdn' => + [ + 'cn-hangzhou' => 'scdn.aliyuncs.com', + ], + 'trademark' => + [ + 'cn-hangzhou' => 'trademark.aliyuncs.com', + ], + 'elasticsearch' => + [ + 'cn-hangzhou' => 'elasticsearch.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'elasticsearch.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'elasticsearch.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'elasticsearch.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'elasticsearch.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'elasticsearch.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'elasticsearch.ap-southeast-3.aliyuncs.com', + 'ap-northeast-1' => 'elasticsearch.ap-northeast-1.aliyuncs.com', + 'us-west-1' => 'elasticsearch.us-west-1.aliyuncs.com', + 'eu-central-1' => 'elasticsearch.eu-central-1.aliyuncs.com', + 'ap-south-1' => 'elasticsearch.ap-south-1.aliyuncs.com', + 'cn-qingdao' => 'elasticsearch.cn-qingdao.aliyuncs.com', + 'ap-southeast-5' => 'elasticsearch.ap-southeast-5.aliyuncs.com', + 'cn-beijing' => 'elasticsearch.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'elasticsearch.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou-finance' => 'elasticsearch.cn-hangzhou-finance.aliyuncs.com', + 'cn-shanghai-finance-1' => 'elasticsearch.cn-shanghai-finance-1.aliyuncs.com', + 'us-east-1' => 'elasticsearch.us-east-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'elasticsearch.cn-north-2-gov-1.aliyuncs.com', + ], + 'luban' => + [ + 'cn-hangzhou' => 'luban.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'luban.cn-shanghai.aliyuncs.com', + ], + 'pcdn' => + [ + 'cn-hangzhou' => 'pcdn.aliyuncs.com', + ], + 'uis' => + [ + 'cn-hangzhou' => 'uis.cn-hangzhou.aliyuncs.com', + 'cn-north-2-gov-1' => 'uis.cn-hangzhou.aliyuncs.com', + ], + 'beebot' => + [ + 'cn-hangzhou' => 'chatbot.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'chatbot.cn-shanghai.aliyuncs.com', + ], + 'chatbot' => + [ + 'global' => 'chatbot.cn-shanghai.aliyuncs.com', + 'cn-shanghai' => 'chatbot.cn-shanghai.aliyuncs.com', + ], + 'alidnsgtm' => + [ + 'cn-hangzhou' => 'alidns.aliyuncs.com', + ], + 'sca' => + [ + 'cn-hangzhou' => 'qualitycheck.cn-hangzhou.aliyuncs.com', + ], + 'cccvn' => + [ + 'cn-shanghai' => 'voicenavigator.cn-shanghai.aliyuncs.com', + ], + 'cloudphoto' => + [ + 'cn-shanghai' => 'cloudphoto.cn-shanghai.aliyuncs.com', + ], + 'smartag' => + [ + 'cn-shanghai' => 'smartag.cn-shanghai.aliyuncs.com', + 'cn-hongkong' => 'smartag.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'smartag.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'smartag.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'smartag.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'smartag.ap-southeast-5.aliyuncs.com', + 'eu-central-1' => 'smartag.eu-central-1.aliyuncs.com', + 'ap-northeast-1' => 'smartag.ap-northeast-1.aliyuncs.com', + 'cn-shanghai-finance-1' => 'smartag.cn-shanghai-finance-1.aliyuncs.com', + ], + 'nlp' => + [ + 'cn-shanghai' => 'nlp.cn-shanghai.aliyuncs.com', + ], + 'nls-cloud-meta' => + [ + 'cn-shanghai' => 'nls-meta.cn-shanghai.aliyuncs.com', + ], + 'nls-filetrans' => + [ + 'cn-shanghai' => 'filetrans.cn-shanghai.aliyuncs.com', + ], + 'linkwan' => + [ + 'cn-shanghai' => 'linkwan.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'linkwan.cn-hangzhou.aliyuncs.com', + ], + 'hdm' => + [ + 'cn-shanghai' => 'hdm-api.aliyuncs.com', + ], + 'iovcc' => + [ + 'cn-shanghai' => 'iovcc.cn-shanghai.aliyuncs.com', + ], + 'ddosdip' => + [ + 'ap-southeast-1' => 'ddosdip.ap-southeast-1.aliyuncs.com', + ], + 'imagesearch' => + [ + 'ap-southeast-1' => 'imagesearch.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'imagesearch.ap-southeast-2.aliyuncs.com', + 'ap-northeast-1' => 'imagesearch.ap-northeast-1.aliyuncs.com', + 'cn-shanghai' => 'imagesearch.cn-shanghai.aliyuncs.com', + ], + 'alidfs' => + [ + 'cn-beijing' => 'dfs.cn-beijing.aliyuncs.com', + 'cn-shanghai' => 'dfs.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'dfs.cn-hangzhou.aliyuncs.com', + 'cn-zhangjiakou' => 'dfs.cn-zhangjiakou.aliyuncs.com', + ], + 'vs' => + [ + 'cn-hangzhou' => 'vs.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'vs.cn-shanghai.aliyuncs.com', + 'cn-qingdao' => 'vs.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'vs.cn-beijing.aliyuncs.com', + 'cn-shenzhen' => 'vs.cn-shenzhen.aliyuncs.com', + ], + 'foas' => + [ + 'cn-qingdao' => 'foas.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'foas.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'foas.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'foas.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'foas.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'foas.cn-shenzhen.aliyuncs.com', + 'ap-northeast-1' => 'foas.ap-northeast-1.aliyuncs.com', + 'ap-southeast-1' => 'foas.ap-southeast-1.aliyuncs.com', + 'ap-southeast-3' => 'foas.ap-southeast-3.aliyuncs.com', + 'cn-hangzhou-finance' => 'foas.cn-hangzhou-finance.aliyuncs.com', + 'cn-shanghai-finance-1' => 'foas.cn-shanghai-finance-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'foas.cn-north-2-gov-1.aliyuncs.com', + 'cn-hongkong' => 'foas.cn-hongkong.aliyuncs.com', + 'eu-central-1' => 'foas.eu-central-1.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'foas.cn-shenzhen-finance-1.aliyuncs.com', + ], + 'iotid' => + [ + 'cn-hangzhou' => 'iotid.cn-hangzhou.aliyuncs.com', + ], + 'drdspost' => + [ + 'ap-southeast-1' => 'drds.ap-southeast-1.aliyuncs.com', + 'cn-shanghai' => 'drds.cn-shanghai.aliyuncs.com', + 'cn-hongkong' => 'drds.cn-hangzhou.aliyuncs.com', + 'cn-huhehaote' => 'drds.cn-huhehaote.aliyuncs.com', + 'us-east-1' => 'drds.us-east-1.aliyuncs.com', + ], + 'drdspre' => + [ + 'cn-qingdao' => 'drds.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'drds.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'drds.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'drds.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'drds.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'drds.cn-hangzhou.aliyuncs.com', + 'cn-huhehaote' => 'drds.cn-huhehaote.aliyuncs.com', + 'us-east-1' => 'drds.us-east-1.aliyuncs.com', + ], + 'acr' => + [ + 'cn-qingdao' => 'cr.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'cr.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'cr.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'cr.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'cr.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'cr.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'cr.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'cr.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'cr.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'cr.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'cr.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'cr.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'cr.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'cr.eu-west-1.aliyuncs.com', + 'us-west-1' => 'cr.us-west-1.aliyuncs.com', + 'us-east-1' => 'cr.us-east-1.aliyuncs.com', + 'eu-central-1' => 'cr.eu-central-1.aliyuncs.com', + 'me-east-1' => 'cr.me-east-1.aliyuncs.com', + 'ap-south-1' => 'cr.ap-south-1.aliyuncs.com', + 'cn-hangzhou-finance' => 'cr.cn-hangzhou-finance.aliyuncs.com', + 'cn-shanghai-finance-1' => 'cr.cn-shanghai-finance-1.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'cr.cn-shenzhen-finance-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'cr.cn-north-2-gov-1.aliyuncs.com', + ], + 'faas' => + [ + 'cn-beijing' => 'faas.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'faas.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'faas.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'faas.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'faas.cn-shenzhen.aliyuncs.com', + 'ap-southeast-5' => 'faas.ap-southeast-5.aliyuncs.com', + 'cn-shanghai-finance-1' => 'faas.cn-shanghai-finance-1.aliyuncs.com', + ], + 'idaas' => + [ + 'cn-hangzhou' => 'idaas.aliyuncs.com', + 'cn-qingdao' => 'idaas.aliyuncs.com', + 'cn-beijing' => 'idaas.aliyuncs.com', + 'cn-chengdu' => 'idaas.aliyuncs.com', + 'cn-zhangjiakou' => 'idaas.aliyuncs.com', + 'cn-huhehaote' => 'idaas.aliyuncs.com', + 'cn-shanghai' => 'idaas.aliyuncs.com', + 'cn-shenzhen' => 'idaas.aliyuncs.com', + 'cn-hongkong' => 'idaas.aliyuncs.com', + 'cn-shanghai-finance-1' => 'idaas.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'idaas.aliyuncs.com', + ], + 'privatelink' => + [ + 'cn-hangzhou' => 'privatelink.cn-hangzhou.aliyuncs.com', + 'cn-huhehaote' => 'privatelink.cn-huhehaote.aliyuncs.com', + 'eu-west-1' => 'privatelink.eu-west-1.aliyuncs.com', + 'ap-southeast-2' => 'privatelink.ap-southeast-2.aliyuncs.com', + 'ap-southeast-5' => 'privatelink.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'privatelink.ap-northeast-1.aliyuncs.com', + 'ap-south-1' => 'privatelink.ap-south-1.aliyuncs.com', + 'cn-shenzhen' => 'privatelink.cn-shenzhen.aliyuncs.com', + ], + 'batchcomputenew' => + [ + 'cn-hongkong' => 'batchcompute.cn-hongkong.aliyuncs.com', + ], + 'vcs' => + [ + 'cn-hangzhou' => 'vcs.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'vcs.cn-shanghai.aliyuncs.com', + ], + 'vds' => + [ + 'cn-hangzhou' => 'vds.aliyuncs.com', + 'cn-shanghai' => 'vds.cn-shanghai.aliyuncs.com', + ], + 'vcsbasic' => + [ + 'cn-hangzhou' => 'vcsbasic.aliyuncs.com', + 'cn-shanghai' => 'vcs.cn-shanghai.aliyuncs.com', + ], + 'hbr' => + [ + 'cn-qingdao' => 'hbr.cn-shanghai.aliyuncs.com', + 'cn-beijing' => 'hbr.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'hbr.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'hbr.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'hbr.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'hbr.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'hbr.cn-shenzhen.aliyuncs.com', + 'ap-southeast-1' => 'hbr.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'hbr.ap-southeast-2.aliyuncs.com', + 'cn-hongkong' => 'hbr.cn-hongkong.aliyuncs.com', + 'ap-southeast-5' => 'hbr.ap-southeast-5.aliyuncs.com', + ], + 'image' => + [ + 'cn-shanghai' => 'image.cn-shanghai.aliyuncs.com', + ], + 'webx' => + [ + 'cn-shenzhen' => 'webplus.cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'webplus.cn-hangzhou.aliyuncs.com', + 'cn-zhangjiakou' => 'webplus.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'webplus.cn-hangzhou.aliyuncs.com', + 'cn-hangzhou' => 'webplus.cn-hangzhou.aliyuncs.com', + ], + 'sddp' => + [ + 'cn-zhangjiakou' => 'sddp.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'sddp.cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'sddp.ap-southeast-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'sddp.cn-north-2-gov-1.aliyuncs.com', + ], + 'oos' => + [ + 'cn-hangzhou' => 'oos.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'oos.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'oos.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'oos.cn-hongkong.aliyuncs.com', + 'us-east-1' => 'oos.us-east-1.aliyuncs.com', + 'cn-beijing' => 'oos.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'oos.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'oos.cn-huhehaote.aliyuncs.com', + 'eu-west-1' => 'oos.eu-west-1.aliyuncs.com', + 'eu-central-1' => 'oos.eu-central-1.aliyuncs.com', + 'ap-south-1' => 'oos.ap-south-1.aliyuncs.com', + 'cn-chengdu' => 'oos.cn-chengdu.aliyuncs.com', + 'ap-southeast-1' => 'oos.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'oos.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'oos.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'oos.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'oos.ap-northeast-1.aliyuncs.com', + ], + 'fnf' => + [ + 'cn-hangzhou' => 'cn-hangzhou.fnf.aliyuncs.com', + 'cn-shanghai' => 'cn-shanghai.fnf.aliyuncs.com', + 'cn-shenzhen' => 'cn-shenzhen.fnf.aliyuncs.com', + 'cn-beijing' => 'cn-beijing.fnf.aliyuncs.com', + ], + 'smc' => + [ + 'cn-huhehaote' => 'smc.aliyuncs.com', + 'cn-hangzhou' => 'smc.aliyuncs.com', + 'cn-qingdao' => 'smc.aliyuncs.com', + 'cn-beijing' => 'smc.aliyuncs.com', + 'cn-zhangjiakou' => 'smc.aliyuncs.com', + 'cn-shanghai' => 'smc.aliyuncs.com', + 'cn-shenzhen' => 'smc.aliyuncs.com', + 'cn-hongkong' => 'smc.aliyuncs.com', + 'ap-southeast-1' => 'smc.aliyuncs.com', + 'ap-southeast-2' => 'smc.aliyuncs.com', + 'ap-southeast-3' => 'smc.aliyuncs.com', + 'ap-southeast-5' => 'smc.aliyuncs.com', + 'ap-northeast-1' => 'smc.aliyuncs.com', + 'eu-west-1' => 'smc.aliyuncs.com', + 'us-west-1' => 'smc.aliyuncs.com', + 'us-east-1' => 'smc.aliyuncs.com', + 'eu-central-1' => 'smc.aliyuncs.com', + 'me-east-1' => 'smc.aliyuncs.com', + 'ap-south-1' => 'smc.aliyuncs.com', + 'cn-chengdu' => 'smc.aliyuncs.com', + ], + 'foasconsole' => + [ + 'cn-beijing' => 'foasconsole.aliyuncs.com', + 'cn-zhangjiakou' => 'foasconsole.aliyuncs.com', + 'cn-hangzhou' => 'foasconsole.aliyuncs.com', + 'cn-shanghai' => 'foasconsole.aliyuncs.com', + 'cn-shenzhen' => 'foasconsole.aliyuncs.com', + 'cn-hongkong' => 'foasconsole.aliyuncs.com', + 'ap-southeast-1' => 'foasconsole.aliyuncs.com', + 'ap-southeast-3' => 'foasconsole.aliyuncs.com', + 'ap-northeast-1' => 'foasconsole.aliyuncs.com', + 'cn-hangzhou-finance' => 'foasconsole.aliyuncs.com', + 'cn-shanghai-finance-1' => 'foasconsole.aliyuncs.com', + 'cn-north-2-gov-1' => 'foasconsole.aliyuncs.com', + 'eu-central-1' => 'foasconsole.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'foasconsole.aliyuncs.com', + ], + 'serverless' => + [ + 'cn-beijing' => 'sae.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'sae.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'sae.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'sae.cn-shenzhen.aliyuncs.com', + ], + 'ivpd' => + [ + 'cn-huhehaote' => 'ivpd.cn-huhehaote.aliyuncs.com', + 'cn-shanghai' => 'ivpd.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'ivpd.cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'ivpd.cn-beijing.aliyuncs.com', + ], + 'hivisengine' => + [ + 'cn-huhehaote' => 'hivisengine.aliyuncs.com', + 'cn-shanghai' => 'hivisengine.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'hivisengine.cn-hangzhou.aliyuncs.com', + ], + 'hiknoengine' => + [ + 'cn-huhehaote' => 'hiknoengine.aliyuncs.com', + 'cn-shanghai' => 'hiknoengine.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'hiknoengine.cn-hangzhou.aliyuncs.com', + ], + 'clouddev' => + [ + 'cn-hangzhou' => 'mpserverless.aliyuncs.com', + 'cn-shanghai' => 'mpserverless.aliyuncs.com', + ], + 'premiumpics' => + [ + 'cn-hangzhou' => 'premiumpics.aliyuncs.com', + ], + 'composer' => + [ + 'cn-hangzhou' => 'composer.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'composer.cn-shanghai.aliyuncs.com', + 'us-east-1' => 'composer.us-east-1.aliyuncs.com', + ], + 'cloudesl' => + [ + 'cn-hangzhou' => 'cloudesl.cn-hangzhou.aliyuncs.com', + ], + 'amscloudapp' => + [ + 'cn-hangzhou' => 'mpca.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'mpca.cn-shanghai.aliyuncs.com', + ], + 'mse' => + [ + 'cn-hangzhou' => 'mse.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'mse.cn-shanghai.aliyuncs.com', + 'cn-beijing' => 'mse.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'mse.cn-zhangjiakou.aliyuncs.com', + 'cn-shenzhen' => 'mse.cn-shenzhen.aliyuncs.com', + ], + 'dg' => + [ + 'cn-hangzhou' => 'dg.cn-hangzhou.aliyuncs.com', + ], + 'graphcompute' => + [ + 'cn-shanghai' => 'gcs.cn-shanghai.aliyuncs.com', + ], + 'cds' => + [ + 'ap-southeast-1' => 'cassandra.aliyuncs.com', + 'cn-qingdao' => 'cassandra.aliyuncs.com', + 'cn-beijing' => 'cassandra.aliyuncs.com', + 'cn-hangzhou' => 'cassandra.aliyuncs.com', + 'cn-shanghai' => 'cassandra.aliyuncs.com', + 'cn-shenzhen' => 'cassandra.aliyuncs.com', + 'cn-hongkong' => 'cassandra.aliyuncs.com', + 'cn-chengdu' => 'cassandra.aliyuncs.com', + 'cn-zhangjiakou' => 'cassandra.aliyuncs.com', + 'cn-huhehaote' => 'cassandra.aliyuncs.com', + 'ap-southeast-2' => 'cassandra.aliyuncs.com', + 'ap-southeast-3' => 'cassandra.aliyuncs.com', + 'ap-southeast-5' => 'cassandra.aliyuncs.com', + 'eu-west-1' => 'cassandra.aliyuncs.com', + 'us-west-1' => 'cassandra.aliyuncs.com', + 'us-east-1' => 'cassandra.aliyuncs.com', + 'eu-central-1' => 'cassandra.aliyuncs.com', + 'me-east-1' => 'cassandra.aliyuncs.com', + 'ap-south-1' => 'cassandra.aliyuncs.com', + ], + 'ads' => + [ + 'cn-qingdao' => 'adb.aliyuncs.com', + 'cn-beijing' => 'adb.aliyuncs.com', + 'cn-zhangjiakou' => 'adb.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'adb.aliyuncs.com', + 'cn-shanghai' => 'adb.aliyuncs.com', + 'cn-shenzhen' => 'adb.aliyuncs.com', + 'cn-hongkong' => 'adb.aliyuncs.com', + 'ap-southeast-1' => 'adb.aliyuncs.com', + 'ap-northeast-1' => 'adb.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'adb.eu-west-1.aliyuncs.com', + 'us-west-1' => 'adb.aliyuncs.com', + 'us-east-1' => 'adb.aliyuncs.com', + 'ap-southeast-2' => 'adb.ap-southeast-2.aliyuncs.com', + 'eu-central-1' => 'adb.eu-central-1.aliyuncs.com', + 'ap-south-1' => 'adb.ap-south-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'adb.aliyuncs.com', + 'cn-chengdu' => 'adb.cn-chengdu.aliyuncs.com', + 'cn-huhehaote' => 'adb.cn-huhehaote.aliyuncs.com', + 'ap-southeast-3' => 'adb.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'adb.ap-southeast-5.aliyuncs.com', + 'cn-hangzhou-finance' => 'adb.aliyuncs.com', + 'cn-shanghai-finance-1' => 'adb.aliyuncs.com', + ], + 'csb' => + [ + 'cn-beijing' => 'csb.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'csb.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'csb.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'csb.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'csb.cn-hongkong.aliyuncs.com', + ], + 'cityvisual' => + [ + 'cn-hangzhou' => 'cityvisual.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'cityvisual.cn-shanghai.aliyuncs.com', + ], + 'dbaudit' => + [ + 'cn-hangzhou' => 'yundun-dbaudit.aliyuncs.com', + 'cn-qingdao' => 'yundun-dbaudit.aliyuncs.com', + 'cn-beijing' => 'yundun-dbaudit.aliyuncs.com', + 'cn-zhangjiakou' => 'yundun-dbaudit.aliyuncs.com', + 'cn-huhehaote' => 'yundun-dbaudit.aliyuncs.com', + 'cn-shanghai' => 'yundun-dbaudit.aliyuncs.com', + 'cn-shenzhen' => 'yundun-dbaudit.aliyuncs.com', + 'cn-hongkong' => 'yundun-dbaudit.aliyuncs.com', + 'ap-southeast-1' => 'yundun-dbaudit.aliyuncs.com', + 'ap-southeast-2' => 'yundun-dbaudit.aliyuncs.com', + 'ap-southeast-3' => 'yundun-dbaudit.aliyuncs.com', + 'ap-southeast-5' => 'yundun-dbaudit.aliyuncs.com', + 'ap-northeast-1' => 'yundun-dbaudit.aliyuncs.com', + 'us-west-1' => 'yundun-dbaudit.aliyuncs.com', + 'us-east-1' => 'yundun-dbaudit.aliyuncs.com', + 'eu-central-1' => 'yundun-dbaudit.aliyuncs.com', + 'me-east-1' => 'yundun-dbaudit.aliyuncs.com', + 'ap-south-1' => 'yundun-dbaudit.aliyuncs.com', + 'cn-shanghai-finance-1' => 'yundun-dbaudit.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'yundun-dbaudit.aliyuncs.com', + 'cn-north-2-gov-1' => 'yundun-dbaudit.aliyuncs.com', + 'cn-chengdu' => 'yundun-dbaudit.aliyuncs.com', + 'eu-west-1' => 'yundun-dbaudit.aliyuncs.com', + ], + 'bssopenapi' => + [ + 'cn-hangzhou' => 'business.aliyuncs.com', + 'cn-shanghai' => 'business.aliyuncs.com', + 'ap-southeast-1' => 'business.ap-southeast-1.aliyuncs.com', + ], + 'indvi' => + [ + 'cn-hangzhou' => 'indvi.cn-hangzhou.aliyuncs.com', + ], + 'swcopyright' => + [ + 'cn-hangzhou' => 'copyright.aliyuncs.com', + ], + 'multimediaai' => + [ + 'cn-beijing' => 'multimediaai.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'multimediaai.cn-hangzhou.aliyuncs.com', + ], + 'rsimganalys' => + [ + 'cn-hangzhou' => 'rsimganalys.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'rsimganalys.cn-shanghai.aliyuncs.com', + ], + 'tdsr' => + [ + 'cn-hangzhou' => 'lyj.cn-hangzhou.aliyuncs.com', + ], + 'eslogstash' => + [ + 'cn-qingdao' => 'elasticsearch.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'elasticsearch.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'elasticsearch.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'elasticsearch.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'elasticsearch.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'elasticsearch.cn-shenzhen.aliyuncs.com', + ], + 'vcoverimage' => + [ + 'cn-beijing' => 'vcoverimage.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'vcoverimage.cn-hangzhou.aliyuncs.com', + ], + 'ahas' => + [ + 'cn-beijing' => 'ahas.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'ahas.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'ahas.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'ahas.cn-shenzhen.aliyuncs.com', + ], + 'vstruction' => + [ + 'cn-beijing' => 'vstruction.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'vstruction.cn-hangzhou.aliyuncs.com', + ], + 'vcovergif' => + [ + 'cn-beijing' => 'vcovergif.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'vcovergif.cn-hangzhou.aliyuncs.com', + ], + 'aiccs' => + [ + 'cn-hangzhou' => 'aiccs.aliyuncs.com', + ], + 'nls' => + [ + 'cn-shanghai' => 'nls-slp.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'nls-slp.cn-shanghai.aliyuncs.com', + ], + 'antcloudauth' => + [ + 'cn-shanghai' => 'antcloudauth.cn-shanghai.aliyuncs.com', + ], + 'prepaid_ads' => + [ + 'cn-hangzhou-finance' => 'ads.cn-hangzhou-finance.aliyuncs.com', + 'cn-beijing' => 'ads.cn-beijing.aliyuncs.com', + 'cn-chengdu' => 'ads.cn-chengdu.aliyuncs.com', + 'cn-zhangjiakou' => 'ads.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'ads.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'ads.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'ads.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'ads.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'ads.ap-southeast-1.aliyuncs.com', + 'ap-southeast-3' => 'ads.ap-southeast-3.aliyuncs.com', + 'ap-northeast-1' => 'ads-share.ap-northeast-1.aliyuncs.com', + 'us-west-1' => 'ads.us-west-1.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'ads.cn-shenzhen-finance-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'ads.cn-north-2-gov-1.aliyuncs.com', + ], + 'hdr' => + [ + 'cn-qingdao' => 'hdr.cn-shanghai.aliyuncs.com', + 'cn-beijing' => 'hdr.cn-shanghai.aliyuncs.com', + 'cn-zhangjiakou' => 'hdr.cn-shanghai.aliyuncs.com', + 'cn-hangzhou' => 'hdr.cn-shanghai.aliyuncs.com', + 'cn-shanghai' => 'hdr.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'hdr.cn-shanghai.aliyuncs.com', + 'cn-hongkong' => 'hdr.cn-shanghai.aliyuncs.com', + 'cn-chengdu' => 'hdr.cn-shanghai.aliyuncs.com', + ], + 'cbs' => + [ + 'cn-qingdao' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-beijing' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-zhangjiakou' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-huhehaote' => 'dbs-api.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-hongkong' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'dbs-api.ap-southeast-1.aliyuncs.com', + 'ap-northeast-1' => 'dbs-api.ap-northeast-1.aliyuncs.com', + 'cn-hangzhou-finance' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-shanghai-finance-1' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'dbs-api.cn-hangzhou.aliyuncs.com', + 'cn-chengdu' => 'dbs.cn-chengdu.aliyuncs.com', + 'ap-southeast-2' => 'dbs.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'dbs.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'dbs.ap-southeast-5.aliyuncs.com', + 'us-west-1' => 'dbs.cn-hangzhou.aliyuncs.com', + 'us-east-1' => 'dbs.cn-hangzhou.aliyuncs.com', + 'eu-central-1' => 'dbs.eu-central-1.aliyuncs.com', + ], + 'datag' => + [ + 'cn-beijing' => 'datag.cn-beijing.aliyuncs.com', + ], + 'retailir' => + [ + 'cn-hangzhou' => 'retailir.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'retailir.cn-shanghai.aliyuncs.com', + ], + 'mpaas' => + [ + 'cn-hangzhou' => 'mpaas.aliyuncs.com', + ], + 'iqa' => + [ + 'cn-hangzhou' => 'iqa.cn-hangzhou.aliyuncs.com', + ], + 'sofa' => + [ + 'cn-hangzhou' => 'sofa.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'sofa.cn-shanghai.aliyuncs.com', + 'cn-hangzhou-finance' => 'sofa.cn-shanghai.aliyuncs.com', + ], + 'edas' => + [ + 'cn-hangzhou' => 'edas.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'edas.cn-shanghai.aliyuncs.com', + 'cn-qingdao' => 'edas.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'edas.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'edas.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'edas.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'edas.cn-shanghai.aliyuncs.com', + 'ap-southeast-1' => 'edas.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'edas.ap-southeast-1.aliyuncs.com', + 'us-east-1' => 'edas.ap-southeast-1.aliyuncs.com', + 'eu-central-1' => 'edas.ap-southeast-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'edas.cn-shanghai.aliyuncs.com', + ], + 'gwsservice' => + [ + 'cn-shanghai' => 'gws.cn-shanghai.aliyuncs.com', + 'cn-beijing' => 'gws.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'gws.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'gws.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'gws.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen' => 'gws.cn-shenzhen.aliyuncs.com', + 'cn-chengdu' => 'gws.cn-chengdu.aliyuncs.com', + 'ap-southeast-1' => 'gws.ap-southeast-1.aliyuncs.com', + 'cn-hongkong' => 'gws.cn-hongkong.aliyuncs.com', + 'ap-southeast-2' => 'gws.ap-southeast-2.aliyuncs.com', + 'us-west-1' => 'gws.us-west-1.aliyuncs.com', + 'us-east-1' => 'gws.us-east-1.aliyuncs.com', + 'eu-central-1' => 'gws.eu-central-1.aliyuncs.com', + 'ap-south-1' => 'gws.ap-south-1.aliyuncs.com', + ], + 'gds' => + [ + 'ap-southeast-1' => 'gdb-api.aliyuncs.com', + 'cn-beijing' => 'gdb-api.aliyuncs.com', + 'cn-zhangjiakou' => 'gdb-api.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'gdb-api.aliyuncs.com', + 'cn-shanghai' => 'gdb-api.aliyuncs.com', + 'cn-shenzhen' => 'gdb-api.aliyuncs.com', + 'ap-southeast-5' => 'gdb-api.ap-southeast-5.aliyuncs.com', + 'cn-qingdao' => 'gdb-api.aliyuncs.com', + 'cn-chengdu' => 'gdb-api.cn-chengdu.aliyuncs.com', + 'cn-huhehaote' => 'gdb-api.cn-huhehaote.aliyuncs.com', + 'cn-hongkong' => 'gdb-api.aliyuncs.com', + 'ap-southeast-2' => 'gdb-api.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'gdb-api.ap-southeast-3.aliyuncs.com', + 'eu-west-1' => 'gdb-api.eu-west-1.aliyuncs.com', + 'us-west-1' => 'gdb-api.aliyuncs.com', + 'us-east-1' => 'gdb-api.aliyuncs.com', + 'eu-central-1' => 'gdb-api.eu-central-1.aliyuncs.com', + 'me-east-1' => 'gdb-api.me-east-1.aliyuncs.com', + 'ap-south-1' => 'gdb-api.ap-south-1.aliyuncs.com', + ], + 'eais' => + [ + 'cn-beijing' => 'eais.cn-beijing.aliyuncs.com', + 'cn-shenzhen' => 'eais.cn-shenzhen.aliyuncs.com', + ], + 'clickhouse' => + [ + 'cn-beijing' => 'clickhouse.aliyuncs.com', + 'cn-hangzhou' => 'clickhouse.aliyuncs.com', + 'cn-shanghai' => 'clickhouse.aliyuncs.com', + 'cn-shenzhen' => 'clickhouse.aliyuncs.com', + 'ap-southeast-1' => 'clickhouse.aliyuncs.com', + ], + 'msepost' => + [ + 'cn-beijing' => 'mse.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'mse.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'mse.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'mse.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'mse.cn-shenzhen.aliyuncs.com', + ], + 'visionai' => + [ + 'cn-beijing' => 'visionai.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'visionai.cn-hangzhou.aliyuncs.com', + ], + 'mseprepaid' => + [ + 'cn-beijing' => 'mse.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'mse.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'mse.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'mse.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'mse.cn-shenzhen.aliyuncs.com', + ], + 'adam' => + [ + 'cn-beijing' => 'adam.cn-beijing.aliyuncs.com', + ], + 'onsmqtt' => + [ + 'cn-beijing' => 'onsmqtt.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'onsmqtt.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'onsmqtt.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'onsmqtt.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'onsmqtt.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'onsmqtt.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'onsmqtt.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'onsmqtt.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'onsmqtt.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'onsmqtt.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'onsmqtt.ap-southeast-5.aliyuncs.com', + 'us-west-1' => 'onsmqtt.us-west-1.aliyuncs.com', + 'eu-central-1' => 'onsmqtt.eu-central-1.aliyuncs.com', + 'cn-shanghai-finance-1' => 'onsmqtt.cn-shanghai-finance-1.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'onsmqtt.cn-shenzhen-finance-1.aliyuncs.com', + ], + 'dypls' => + [ + 'cn-hangzhou' => 'dyplsapi.aliyuncs.com', + ], + 'resourcemanager' => + [ + 'cn-hangzhou' => 'resourcemanager.aliyuncs.com', + 'cn-shanghai' => 'resourcemanager.aliyuncs.com', + ], + 'nlpautoml' => + [ + 'cn-hangzhou' => 'nlp-automl.cn-hangzhou.aliyuncs.com', + ], + 'companyreg' => + [ + 'cn-hangzhou' => 'companyreg.aliyuncs.com', + ], + 'aliyuncvc' => + [ + 'cn-hangzhou' => 'aliyuncvc.cn-hangzhou.aliyuncs.com', + ], + 'alimtautoml' => + [ + 'cn-hangzhou' => 'alimtautoml.cn-hangzhou.aliyuncs.com', + ], + 'dbfs' => + [ + 'cn-hangzhou' => 'dbfs.cn-hangzhou.aliyuncs.com', + 'cn-qingdao' => 'dbfs.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'dbfs.cn-beijing.aliyuncs.com', + 'cn-chengdu' => 'dbfs.cn-chengdu.aliyuncs.com', + 'cn-zhangjiakou' => 'dbfs.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'dbfs.cn-huhehaote.aliyuncs.com', + 'cn-shanghai' => 'dbfs.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'dbfs.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'dbfs.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'dbfs.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'dbfs.ap-southeast-2.aliyuncs.com', + 'ap-southeast-3' => 'dbfs.ap-southeast-3.aliyuncs.com', + 'ap-southeast-5' => 'dbfs.ap-southeast-5.aliyuncs.com', + 'ap-northeast-1' => 'dbfs.ap-northeast-1.aliyuncs.com', + 'eu-west-1' => 'dbfs.eu-west-1.aliyuncs.com', + 'us-west-1' => 'dbfs.us-west-1.aliyuncs.com', + 'us-east-1' => 'dbfs.us-east-1.aliyuncs.com', + 'eu-central-1' => 'dbfs.eu-central-1.aliyuncs.com', + 'me-east-1' => 'dbfs.me-east-1.aliyuncs.com', + 'ap-south-1' => 'dbfs.ap-south-1.aliyuncs.com', + ], + 'addrp' => + [ + 'cn-hangzhou' => 'address-purification.cn-hangzhou.aliyuncs.com', + ], + 'gaplus' => + [ + 'cn-hangzhou' => 'ga.cn-hangzhou.aliyuncs.com', + ], + 'datav' => + [ + 'cn-hangzhou' => 'datav.cn-hangzhou.aliyuncs.com', + ], + 'ocr' => + [ + 'cn-shanghai' => 'ocr.cn-shanghai.aliyuncs.com', + ], + 'objectdet' => + [ + 'cn-shanghai' => 'objectdet.cn-shanghai.aliyuncs.com', + ], + 'assetservice' => + [ + 'cn-shanghai' => 'assettech.cn-shanghai.aliyuncs.com', + ], + 'imageenhan' => + [ + 'cn-shanghai' => 'imageenhan.cn-shanghai.aliyuncs.com', + ], + 'imageaudit' => + [ + 'cn-shanghai' => 'imageaudit.cn-shanghai.aliyuncs.com', + ], + 'imagerecog' => + [ + 'cn-shanghai' => 'imagerecog.cn-shanghai.aliyuncs.com', + ], + 'imageseg' => + [ + 'cn-shanghai' => 'imageseg.cn-shanghai.aliyuncs.com', + ], + 'goodstech' => + [ + 'cn-shanghai' => 'goodstech.cn-shanghai.aliyuncs.com', + ], + 'facebody' => + [ + 'cn-shanghai' => 'facebody.cn-shanghai.aliyuncs.com', + ], + 'voicebot' => + [ + 'cn-hangzhou' => 'voicenavigator.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'voicenavigator.cn-shanghai.aliyuncs.com', + ], + 'dyiot' => + [ + 'cn-hangzhou' => 'dyiotapi.aliyuncs.com', + ], + 'drp' => + [ + 'cn-hangzhou' => 'drp-share.cn-hangzhou.aliyuncs.com', + ], + 'uem' => + [ + 'cn-hangzhou' => 'uem.aliyuncs.com', + ], + 'outboundbot' => + [ + 'cn-hangzhou' => 'outboundbot.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'outboundbot.cn-shanghai.aliyuncs.com', + ], + 'hcs_hgw' => + [ + 'cn-shanghai' => 'hgw.cn-shanghai.aliyuncs.com', + ], + 'acms' => + [ + 'cn-qingdao' => 'acm.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'acm.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'acm.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'acm.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'acm.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'acm.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'acm.cn-hongkong.aliyuncs.com', + 'ap-southeast-1' => 'acm.ap-southeast-1.aliyuncs.com', + 'ap-southeast-2' => 'acm.ap-southeast-2.aliyuncs.com', + 'us-west-1' => 'acm.us-west-1.aliyuncs.com', + 'us-east-1' => 'acm.us-east-1.aliyuncs.com', + 'eu-central-1' => 'acm.eu-central-1.aliyuncs.com', + 'cn-hangzhou-finance' => 'acm.cn-hangzhou-finance.aliyuncs.com', + 'cn-shanghai-finance-1' => 'acm.cn-shanghai-finance-1.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'acm.cn-shenzhen-finance-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'acm.cn-north-2-gov-1.aliyuncs.com', + ], + 'onsproxy' => + [ + 'cn-qingdao' => 'amqp-open.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'amqp-open.cn-beijing.aliyuncs.com', + 'cn-hangzhou' => 'amqp-open.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'amqp-open.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'amqp-open.cn-shenzhen.aliyuncs.com', + ], + 'drdsro' => + [ + 'cn-qingdao' => 'drds.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'drds.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'drds.cn-zhangjiakou.aliyuncs.com', + 'cn-huhehaote' => 'drds.cn-huhehaote.aliyuncs.com', + 'cn-hangzhou' => 'drds.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'drds.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'drds.cn-shenzhen.aliyuncs.com', + 'cn-hongkong' => 'drds.cn-hangzhou.aliyuncs.com', + 'ap-southeast-1' => 'drds.ap-southeast-1.aliyuncs.com', + 'us-east-1' => 'drds.us-east-1.aliyuncs.com', + 'cn-shanghai-finance-1' => 'drds.cn-hangzhou.aliyuncs.com', + 'cn-shenzhen-finance-1' => 'drds.cn-hangzhou.aliyuncs.com', + 'cn-north-2-gov-1' => 'drds.cn-hangzhou.aliyuncs.com', + ], + 'opensearch' => + [ + 'cn-qingdao' => 'opensearch.cn-qingdao.aliyuncs.com', + 'cn-beijing' => 'opensearch.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'opensearch.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'opensearch.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'opensearch.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'opensearch.cn-shenzhen.aliyuncs.com', + 'ap-southeast-1' => 'opensearch.ap-southeast-1.aliyuncs.com', + 'cn-north-2-gov-1' => 'opensearch.cn-north-2-gov-1.aliyuncs.com', + ], + 'edasschedulerx' => + [ + 'cn-beijing' => 'schedulerx.cn-beijing.aliyuncs.com', + 'cn-zhangjiakou' => 'schedulerx.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'schedulerx.cn-hangzhou.aliyuncs.com', + 'cn-shanghai' => 'schedulerx.cn-shanghai.aliyuncs.com', + 'cn-shenzhen' => 'schedulerx.cn-shenzhen.aliyuncs.com', + ], + 'tag' => + [ + 'cn-beijing' => 'tag.aliyuncs.com', + 'cn-shenzhen' => 'tag.aliyuncs.com', + ], + 'servicemesh' => + [ + 'cn-beijing' => 'servicemesh.aliyuncs.com', + 'cn-zhangjiakou' => 'servicemesh.aliyuncs.com', + 'cn-hangzhou' => 'servicemesh.aliyuncs.com', + 'cn-shanghai' => 'servicemesh.aliyuncs.com', + 'cn-shenzhen' => 'servicemesh.aliyuncs.com', + 'ap-southeast-1' => 'servicemesh.aliyuncs.com', + 'us-west-1' => 'servicemesh.aliyuncs.com', + ], + 'rdc' => + [ + 'cn-beijing' => 'rdc.cn-beijing.aliyuncs.com', + ], + 'sddprsrc' => + [ + 'cn-zhangjiakou' => 'sddp-rsrc.cn-zhangjiakou.aliyuncs.com', + 'cn-hangzhou' => 'sddprsrc.cn-hangzhou.aliyuncs.com', + 'cn-north-2-gov-1' => 'sddprsrc.cn-north-2-gov-1.aliyuncs.com', + ], + 'polardbx' => + [ + 'cn-hangzhou' => 'polardbx.cn-hangzhou.aliyuncs.com', + ], + 'dytns' => + [ + 'cn-hangzhou' => 'dytnsapi.aliyuncs.com', + ], + 'datahub' => + [ + 'cn-hangzhou' => 'datahub.aliyuncs.com', + 'cn-shanghai' => 'datahub.aliyuncs.com', + ], + 'geoip' => + [ + 'cn-hangzhou' => 'geoip.aliyuncs.com', + ], + 'digitalstore' => + [ + 'cn-hangzhou' => 'digitalstore.cn-hangzhou.aliyuncs.com', + ], + 'quickbi' => + [ + 'cn-hangzhou' => 'quickbi-public-share.aliyuncs.com', + 'cn-hongkong' => 'quickbi-public-share.aliyuncs.com', + 'ap-southeast-1' => 'quickbi-public-share.aliyuncs.com', + 'ap-southeast-3' => 'quickbi-public-share.aliyuncs.com', + 'eu-central-1' => 'quickbi-public-share.aliyuncs.com', + ], + 'alimtdt' => + [ + 'cn-hangzhou' => 'mt.cn-hangzhou.aliyuncs.com', + ], + 'sofamq' => + [ + 'cn-shanghai' => 'sofa.cn-shanghai.aliyuncs.com', + 'cn-hangzhou-finance' => 'sofa.cn-hangzhou-finance.aliyuncs.com', + ], + 'sofaodp' => + [ + 'cn-shanghai' => 'sofa.cn-shanghai.aliyuncs.com', + 'cn-hangzhou-finance' => 'sofa.cn-hangzhou-finance.aliyuncs.com', + ], + 'dascharge' => + [ + 'cn-shanghai' => 'das.aliyuncs.com', + ], + 'sofadst' => + [ + 'cn-shanghai' => 'sofa.cn-shanghai.aliyuncs.com', + ], + ], +]; diff --git a/vendor/alibabacloud/client/src/Credentials/AccessKeyCredential.php b/vendor/alibabacloud/client/src/Credentials/AccessKeyCredential.php new file mode 100644 index 0000000..bacaecc --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/AccessKeyCredential.php @@ -0,0 +1,65 @@ +accessKeyId = $accessKeyId; + $this->accessKeySecret = $accessKeySecret; + } + + /** + * @return string + */ + public function getAccessKeyId() + { + return $this->accessKeyId; + } + + /** + * @return string + */ + public function getAccessKeySecret() + { + return $this->accessKeySecret; + } + + /** + * @return string + */ + public function __toString() + { + return "$this->accessKeyId#$this->accessKeySecret"; + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/BearerTokenCredential.php b/vendor/alibabacloud/client/src/Credentials/BearerTokenCredential.php new file mode 100644 index 0000000..db69a7c --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/BearerTokenCredential.php @@ -0,0 +1,66 @@ +bearerToken = $bearerToken; + } + + /** + * @return string + */ + public function getBearerToken() + { + return $this->bearerToken; + } + + /** + * @return string + */ + public function getAccessKeyId() + { + return ''; + } + + /** + * @return string + */ + public function getAccessKeySecret() + { + return ''; + } + + /** + * @return string + */ + public function __toString() + { + return "bearerToken#$this->bearerToken"; + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/CredentialsInterface.php b/vendor/alibabacloud/client/src/Credentials/CredentialsInterface.php new file mode 100644 index 0000000..96ee50a --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/CredentialsInterface.php @@ -0,0 +1,18 @@ +roleName = $roleName; + } + + /** + * @return string + */ + public function getRoleName() + { + return $this->roleName; + } + + /** + * @return string + */ + public function __toString() + { + return "roleName#$this->roleName"; + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/Ini/CreateTrait.php b/vendor/alibabacloud/client/src/Credentials/Ini/CreateTrait.php new file mode 100644 index 0000000..2ec7511 --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/Ini/CreateTrait.php @@ -0,0 +1,181 @@ +missingRequired('type', $clientName); + } + + return $this->createClientByType($clientName, $credential)->name($clientName); + } + + /** + * @param string $clientName + * @param array $credential + * + * @return AccessKeyClient|BearerTokenClient|EcsRamRoleClient|RamRoleArnClient|RsaKeyPairClient + * @throws ClientException + */ + private function createClientByType($clientName, array $credential) + { + switch (\strtolower($credential['type'])) { + case 'access_key': + return $this->accessKeyClient($clientName, $credential); + case 'ecs_ram_role': + return $this->ecsRamRoleClient($clientName, $credential); + case 'ram_role_arn': + return $this->ramRoleArnClient($clientName, $credential); + case 'bearer_token': + return $this->bearerTokenClient($clientName, $credential); + case 'rsa_key_pair': + return $this->rsaKeyPairClient($clientName, $credential); + default: + throw new ClientException( + "Invalid type '{$credential['type']}' for '$clientName' in {$this->filename}", + SDK::INVALID_CREDENTIAL + ); + } + } + + /** + * @param array $credential + * @param string $clientName + * + * @return AccessKeyClient + * @throws ClientException + */ + private function accessKeyClient($clientName, array $credential) + { + if (!isset($credential['access_key_id'])) { + $this->missingRequired('access_key_id', $clientName); + } + + if (!isset($credential['access_key_secret'])) { + $this->missingRequired('access_key_secret', $clientName); + } + + return new AccessKeyClient( + $credential['access_key_id'], + $credential['access_key_secret'] + ); + } + + /** + * @param string $clientName + * @param array $credential + * + * @return EcsRamRoleClient + * @throws ClientException + */ + private function ecsRamRoleClient($clientName, array $credential) + { + if (!isset($credential['role_name'])) { + $this->missingRequired('role_name', $clientName); + } + + return new EcsRamRoleClient($credential['role_name']); + } + + /** + * @param string $clientName + * @param array $credential + * + * @return RamRoleArnClient + * @throws ClientException + */ + private function ramRoleArnClient($clientName, array $credential) + { + if (!isset($credential['access_key_id'])) { + $this->missingRequired('access_key_id', $clientName); + } + + if (!isset($credential['access_key_secret'])) { + $this->missingRequired('access_key_secret', $clientName); + } + + if (!isset($credential['role_arn'])) { + $this->missingRequired('role_arn', $clientName); + } + + if (!isset($credential['role_session_name'])) { + $this->missingRequired('role_session_name', $clientName); + } + + return new RamRoleArnClient( + $credential['access_key_id'], + $credential['access_key_secret'], + $credential['role_arn'], + $credential['role_session_name'] + ); + } + + /** + * @param string $clientName + * @param array $credential + * + * @return BearerTokenClient + * @throws ClientException + */ + private function bearerTokenClient($clientName, array $credential) + { + if (!isset($credential['bearer_token'])) { + $this->missingRequired('bearer_token', $clientName); + } + + return new BearerTokenClient($credential['bearer_token']); + } + + /** + * @param array $credential + * @param string $clientName + * + * @return RsaKeyPairClient + * @throws ClientException + */ + private function rsaKeyPairClient($clientName, array $credential) + { + if (!isset($credential['public_key_id'])) { + $this->missingRequired('public_key_id', $clientName); + } + + if (!isset($credential['private_key_file'])) { + $this->missingRequired('private_key_file', $clientName); + } + + return new RsaKeyPairClient( + $credential['public_key_id'], + $credential['private_key_file'] + ); + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/Ini/IniCredential.php b/vendor/alibabacloud/client/src/Credentials/Ini/IniCredential.php new file mode 100644 index 0000000..94ec7fb --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/Ini/IniCredential.php @@ -0,0 +1,209 @@ +filename = $filename ?: $this->getDefaultFile(); + } + + /** + * Get the default credential file. + * + * @return string + */ + public function getDefaultFile() + { + return self::getHomeDirectory() . DIRECTORY_SEPARATOR . '.alibabacloud' . DIRECTORY_SEPARATOR . 'credentials'; + } + + /** + * Gets the environment's HOME directory. + * + * @return null|string + */ + private static function getHomeDirectory() + { + if (getenv('HOME')) { + return getenv('HOME'); + } + + return (getenv('HOMEDRIVE') && getenv('HOMEPATH')) + ? getenv('HOMEDRIVE') . getenv('HOMEPATH') + : null; + } + + /** + * Clear credential cache. + * + * @return void + */ + public static function forgetLoadedCredentialsFile() + { + self::$hasLoaded = []; + } + + /** + * Get the credential file. + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + + /** + * @param array $array + * @param string $key + * + * @return bool + */ + protected static function isNotEmpty(array $array, $key) + { + return isset($array[$key]) && !empty($array[$key]); + } + + /** + * @param string $key + * @param string $clientName + * + * @throws ClientException + */ + public function missingRequired($key, $clientName) + { + throw new ClientException( + "Missing required '$key' option for '$clientName' in " . $this->getFilename(), + SDK::INVALID_CREDENTIAL + ); + } + + /** + * @return array|mixed + * @throws ClientException + */ + public function load() + { + // If it has been loaded, assign the client directly. + if (isset(self::$hasLoaded[$this->filename])) { + /** + * @var $client Client + */ + foreach (self::$hasLoaded[$this->filename] as $projectName => $client) { + $client->name($projectName); + } + + return self::$hasLoaded[$this->filename]; + } + + return $this->loadFile(); + } + + /** + * Exceptions will be thrown if the file is unreadable and not the default file. + * + * @return array|mixed + * @throws ClientException + */ + private function loadFile() + { + if (!\AlibabaCloud\Client\inOpenBasedir($this->filename)) { + return []; + } + + if (!\is_readable($this->filename) || !\is_file($this->filename)) { + if ($this->filename === $this->getDefaultFile()) { + // @codeCoverageIgnoreStart + return []; + // @codeCoverageIgnoreEnd + } + throw new ClientException( + 'Credential file is not readable: ' . $this->getFilename(), + SDK::INVALID_CREDENTIAL + ); + } + + return $this->parseFile(); + } + + /** + * Decode the ini file into an array. + * + * @return array|mixed + * @throws ClientException + */ + private function parseFile() + { + try { + $file = \parse_ini_file($this->filename, true); + if (\is_array($file) && $file !== []) { + return $this->initClients($file); + } + throw new ClientException( + 'Format error: ' . $this->getFilename(), + SDK::INVALID_CREDENTIAL + ); + } catch (\Exception $e) { + throw new ClientException( + $e->getMessage(), + SDK::INVALID_CREDENTIAL, + $e + ); + } + } + + /** + * Initialize clients. + * + * @param array $array + * + * @return array|mixed + * @throws ClientException + */ + private function initClients($array) + { + foreach (\array_change_key_case($array) as $clientName => $configures) { + $configures = \array_change_key_case($configures); + $clientInstance = $this->createClient($clientName, $configures); + if ($clientInstance instanceof Client) { + self::$hasLoaded[$this->filename][$clientName] = $clientInstance; + self::setClientAttributes($configures, $clientInstance); + self::setCert($configures, $clientInstance); + self::setProxy($configures, $clientInstance); + } + } + + return isset(self::$hasLoaded[$this->filename]) + ? self::$hasLoaded[$this->filename] + : []; + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/Ini/OptionsTrait.php b/vendor/alibabacloud/client/src/Credentials/Ini/OptionsTrait.php new file mode 100644 index 0000000..192d494 --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/Ini/OptionsTrait.php @@ -0,0 +1,111 @@ +regionId($configures['region_id']); + } + + if (isset($configures['debug'])) { + $client->options( + [ + 'debug' => (bool)$configures['debug'], + ] + ); + } + + if (self::isNotEmpty($configures, 'timeout')) { + $client->options( + [ + 'timeout' => $configures['timeout'], + ] + ); + } + + if (self::isNotEmpty($configures, 'connect_timeout')) { + $client->options( + [ + 'connect_timeout' => $configures['connect_timeout'], + ] + ); + } + } + + /** + * @param array $configures + * @param Client $client + */ + private static function setProxy($configures, Client $client) + { + if (self::isNotEmpty($configures, 'proxy')) { + $client->options( + [ + 'proxy' => $configures['proxy'], + ] + ); + } + $proxy = []; + if (self::isNotEmpty($configures, 'proxy_http')) { + $proxy['http'] = $configures['proxy_http']; + } + if (self::isNotEmpty($configures, 'proxy_https')) { + $proxy['https'] = $configures['proxy_https']; + } + if (self::isNotEmpty($configures, 'proxy_no')) { + $proxy['no'] = \explode(',', $configures['proxy_no']); + } + if ($proxy !== []) { + $client->options( + [ + 'proxy' => $proxy, + ] + ); + } + } + + /** + * @param array $configures + * @param Client $client + */ + private static function setCert($configures, Client $client) + { + if (self::isNotEmpty($configures, 'cert_file') && !self::isNotEmpty($configures, 'cert_password')) { + $client->options( + [ + 'cert' => $configures['cert_file'], + ] + ); + } + + if (self::isNotEmpty($configures, 'cert_file') && self::isNotEmpty($configures, 'cert_password')) { + $client->options( + [ + 'cert' => [ + $configures['cert_file'], + $configures['cert_password'], + ], + ] + ); + } + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/Providers/CredentialsProvider.php b/vendor/alibabacloud/client/src/Credentials/Providers/CredentialsProvider.php new file mode 100644 index 0000000..21aec9b --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/Providers/CredentialsProvider.php @@ -0,0 +1,170 @@ +asDefaultClient(); + } + }; + } + + /** + * @return Closure + */ + public static function ini() + { + return static function () { + $ini = \AlibabaCloud\Client\envNotEmpty('ALIBABA_CLOUD_CREDENTIALS_FILE'); + + if ($ini) { + AlibabaCloud::load($ini); + } else { + // @codeCoverageIgnoreStart + AlibabaCloud::load(); + // @codeCoverageIgnoreEnd + } + + self::compatibleWithGlobal(); + }; + } + + /** + * @codeCoverageIgnore + * + * Compatible with global + * + * @throws ClientException + */ + private static function compatibleWithGlobal() + { + if (AlibabaCloud::has('global') && !AlibabaCloud::has(self::getDefaultName())) { + AlibabaCloud::get('global')->name(self::getDefaultName()); + } + } + + /** + * @return array|false|string + * @throws ClientException + */ + public static function getDefaultName() + { + $name = \AlibabaCloud\Client\envNotEmpty('ALIBABA_CLOUD_PROFILE'); + + if ($name) { + return $name; + } + + return 'default'; + } + + /** + * @return Closure + */ + public static function instance() + { + return static function () { + $instance = \AlibabaCloud\Client\envNotEmpty('ALIBABA_CLOUD_ECS_METADATA'); + if ($instance) { + AlibabaCloud::ecsRamRoleClient($instance)->asDefaultClient(); + } + }; + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/Providers/EcsRamRoleProvider.php b/vendor/alibabacloud/client/src/Credentials/Providers/EcsRamRoleProvider.php new file mode 100644 index 0000000..26bb675 --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/Providers/EcsRamRoleProvider.php @@ -0,0 +1,128 @@ +getCredentialsInCache(); + + if ($result === null) { + $result = $this->request(); + + if (!isset($result['AccessKeyId'], $result['AccessKeySecret'], $result['SecurityToken'])) { + throw new ServerException($result, $this->error, SDK::INVALID_CREDENTIAL); + } + + $this->cache($result->toArray()); + } + + return new StsCredential( + $result['AccessKeyId'], + $result['AccessKeySecret'], + $result['SecurityToken'] + ); + } + + /** + * Get credentials by request. + * + * @return Result + * @throws ClientException + * @throws ServerException + */ + public function request() + { + $result = $this->getResponse(); + + if ($result->getStatusCode() === 404) { + $message = 'The role was not found in the instance'; + throw new ClientException($message, SDK::INVALID_CREDENTIAL); + } + + if (!$result->isSuccess()) { + $message = 'Error retrieving credentials from result'; + throw new ServerException($result, $message, SDK::INVALID_CREDENTIAL); + } + + return $result; + } + + /** + * Get data from meta. + * + * @return mixed|ResponseInterface + * @throws ClientException + * @throws Exception + */ + public function getResponse() + { + /** + * @var EcsRamRoleCredential $credential + */ + $credential = $this->client->getCredential(); + $url = $this->uri . $credential->getRoleName(); + + $options = [ + 'http_errors' => false, + 'timeout' => 1, + 'connect_timeout' => 1, + 'debug' => $this->client->isDebug(), + ]; + + try { + return RpcRequest::createClient()->request('GET', $url, $options); + } catch (GuzzleException $exception) { + if (Stringy::create($exception->getMessage())->contains('timed')) { + $message = 'Timeout or instance does not belong to Alibaba Cloud'; + } else { + $message = $exception->getMessage(); + } + + throw new ClientException( + $message, + SDK::SERVER_UNREACHABLE, + $exception + ); + } + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/Providers/Provider.php b/vendor/alibabacloud/client/src/Credentials/Providers/Provider.php new file mode 100644 index 0000000..b64dab8 --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/Providers/Provider.php @@ -0,0 +1,88 @@ +client = $client; + } + + /** + * Get the credentials from the cache in the validity period. + * + * @return array|null + */ + public function getCredentialsInCache() + { + if (isset(self::$credentialsCache[$this->key()])) { + $result = self::$credentialsCache[$this->key()]; + if (\strtotime($result['Expiration']) - \time() >= $this->expirationSlot) { + return $result; + } + unset(self::$credentialsCache[$this->key()]); + } + + return null; + } + + /** + * Get the toString of the credentials as the key. + * + * @return string + */ + protected function key() + { + return (string)$this->client->getCredential(); + } + + /** + * Cache credentials. + * + * @param array $credential + */ + protected function cache(array $credential) + { + self::$credentialsCache[$this->key()] = $credential; + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/Providers/RamRoleArnProvider.php b/vendor/alibabacloud/client/src/Credentials/Providers/RamRoleArnProvider.php new file mode 100644 index 0000000..ce4de41 --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/Providers/RamRoleArnProvider.php @@ -0,0 +1,84 @@ +getCredentialsInCache(); + + if (null === $credential) { + $result = $this->request($timeout, $connectTimeout); + + if (!isset($result['Credentials']['AccessKeyId'], + $result['Credentials']['AccessKeySecret'], + $result['Credentials']['SecurityToken'])) { + throw new ServerException($result, $this->error, SDK::INVALID_CREDENTIAL); + } + + $credential = $result['Credentials']; + $this->cache($credential); + } + + return new StsCredential( + $credential['AccessKeyId'], + $credential['AccessKeySecret'], + $credential['SecurityToken'] + ); + } + + /** + * Get credentials by request. + * + * @param $timeout + * @param $connectTimeout + * + * @return Result + * @throws ClientException + * @throws ServerException + */ + private function request($timeout, $connectTimeout) + { + $clientName = __CLASS__ . \uniqid('ak', true); + $credential = $this->client->getCredential(); + + AlibabaCloud::accessKeyClient( + $credential->getAccessKeyId(), + $credential->getAccessKeySecret() + )->name($clientName); + + return (new AssumeRole($credential)) + ->client($clientName) + ->timeout($timeout) + ->connectTimeout($connectTimeout) + ->debug($this->client->isDebug()) + ->request(); + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/Providers/RsaKeyPairProvider.php b/vendor/alibabacloud/client/src/Credentials/Providers/RsaKeyPairProvider.php new file mode 100644 index 0000000..e78fc1c --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/Providers/RsaKeyPairProvider.php @@ -0,0 +1,86 @@ +getCredentialsInCache(); + + if ($credential === null) { + $result = $this->request($timeout, $connectTimeout); + + if (!isset($result['SessionAccessKey']['SessionAccessKeyId'], + $result['SessionAccessKey']['SessionAccessKeySecret'])) { + throw new ServerException($result, $this->error, SDK::INVALID_CREDENTIAL); + } + + $credential = $result['SessionAccessKey']; + $this->cache($credential); + } + + return new StsCredential( + $credential['SessionAccessKeyId'], + $credential['SessionAccessKeySecret'] + ); + } + + /** + * Get credentials by request. + * + * @param $timeout + * @param $connectTimeout + * + * @return Result + * @throws ClientException + * @throws ServerException + */ + private function request($timeout, $connectTimeout) + { + $clientName = __CLASS__ . \uniqid('rsa', true); + $credential = $this->client->getCredential(); + + AlibabaCloud::client( + new AccessKeyCredential( + $credential->getPublicKeyId(), + $credential->getPrivateKey() + ), + new ShaHmac256WithRsaSignature() + )->name($clientName); + + return (new GenerateSessionAccessKey($credential->getPublicKeyId())) + ->client($clientName) + ->timeout($timeout) + ->connectTimeout($connectTimeout) + ->debug($this->client->isDebug()) + ->request(); + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/RamRoleArnCredential.php b/vendor/alibabacloud/client/src/Credentials/RamRoleArnCredential.php new file mode 100644 index 0000000..6bdf5be --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/RamRoleArnCredential.php @@ -0,0 +1,110 @@ +accessKeyId = $accessKeyId; + $this->accessKeySecret = $accessKeySecret; + $this->roleArn = $roleArn; + $this->roleSessionName = $roleSessionName; + $this->policy = $policy; + } + + /** + * @return string + */ + public function getAccessKeyId() + { + return $this->accessKeyId; + } + + /** + * @return string + */ + public function getAccessKeySecret() + { + return $this->accessKeySecret; + } + + /** + * @return string + */ + public function getRoleArn() + { + return $this->roleArn; + } + + /** + * @return string + */ + public function getRoleSessionName() + { + return $this->roleSessionName; + } + + /** + * @return string + */ + public function getPolicy() + { + return $this->policy; + } + + /** + * @return string + */ + public function __toString() + { + return "$this->accessKeyId#$this->accessKeySecret#$this->roleArn#$this->roleSessionName"; + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/Requests/AssumeRole.php b/vendor/alibabacloud/client/src/Credentials/Requests/AssumeRole.php new file mode 100644 index 0000000..a3935aa --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/Requests/AssumeRole.php @@ -0,0 +1,47 @@ +product('Sts'); + $this->version('2015-04-01'); + $this->action('AssumeRole'); + $this->host('sts.aliyuncs.com'); + $this->scheme('https'); + $this->regionId('cn-hangzhou'); + $this->options['verify'] = false; + $this->options['query']['RoleArn'] = $arnCredential->getRoleArn(); + $this->options['query']['RoleSessionName'] = $arnCredential->getRoleSessionName(); + $this->options['query']['DurationSeconds'] = Provider::DURATION_SECONDS; + if ($arnCredential->getPolicy()) { + if (is_array($arnCredential->getPolicy())) { + $this->options['query']['Policy'] = json_encode($arnCredential->getPolicy()); + } + if (is_string($arnCredential->getPolicy())) { + $this->options['query']['Policy'] = $arnCredential->getPolicy(); + } + } + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/Requests/GenerateSessionAccessKey.php b/vendor/alibabacloud/client/src/Credentials/Requests/GenerateSessionAccessKey.php new file mode 100644 index 0000000..4ac1ee1 --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/Requests/GenerateSessionAccessKey.php @@ -0,0 +1,37 @@ +product('Sts'); + $this->version('2015-04-01'); + $this->action('GenerateSessionAccessKey'); + $this->host('sts.ap-northeast-1.aliyuncs.com'); + $this->scheme('https'); + $this->regionId('cn-hangzhou'); + $this->options['verify'] = false; + $this->options['query']['PublicKeyId'] = $publicKeyId; + $this->options['query']['DurationSeconds'] = Provider::DURATION_SECONDS; + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/RsaKeyPairCredential.php b/vendor/alibabacloud/client/src/Credentials/RsaKeyPairCredential.php new file mode 100644 index 0000000..876909e --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/RsaKeyPairCredential.php @@ -0,0 +1,75 @@ +publicKeyId = $publicKeyId; + try { + $this->privateKey = file_get_contents($privateKeyFile); + } catch (Exception $exception) { + throw new ClientException( + $exception->getMessage(), + SDK::INVALID_CREDENTIAL + ); + } + } + + /** + * @return mixed + */ + public function getPrivateKey() + { + return $this->privateKey; + } + + /** + * @return string + */ + public function getPublicKeyId() + { + return $this->publicKeyId; + } + + /** + * @return string + */ + public function __toString() + { + return "publicKeyId#$this->publicKeyId"; + } +} diff --git a/vendor/alibabacloud/client/src/Credentials/StsCredential.php b/vendor/alibabacloud/client/src/Credentials/StsCredential.php new file mode 100644 index 0000000..d333fa8 --- /dev/null +++ b/vendor/alibabacloud/client/src/Credentials/StsCredential.php @@ -0,0 +1,80 @@ +accessKeyId = $accessKeyId; + $this->accessKeySecret = $accessKeySecret; + $this->securityToken = $securityToken; + } + + /** + * @return string + */ + public function getAccessKeyId() + { + return $this->accessKeyId; + } + + /** + * @return string + */ + public function getAccessKeySecret() + { + return $this->accessKeySecret; + } + + /** + * @return string + */ + public function getSecurityToken() + { + return $this->securityToken; + } + + /** + * @return string + */ + public function __toString() + { + return "$this->accessKeyId#$this->accessKeySecret#$this->securityToken"; + } +} diff --git a/vendor/alibabacloud/client/src/DefaultAcsClient.php b/vendor/alibabacloud/client/src/DefaultAcsClient.php new file mode 100644 index 0000000..ff5ecd7 --- /dev/null +++ b/vendor/alibabacloud/client/src/DefaultAcsClient.php @@ -0,0 +1,55 @@ +randClientName = \uniqid('', true); + $client->name($this->randClientName); + } + + /** + * @param Request|Result $request + * + * @return Result|string + * @throws ClientException + * @throws ServerException + */ + public function getAcsResponse($request) + { + if ($request instanceof Result) { + return $request; + } + + return $request->client($this->randClientName)->request(); + } +} diff --git a/vendor/alibabacloud/client/src/Encode.php b/vendor/alibabacloud/client/src/Encode.php new file mode 100644 index 0000000..ef589d2 --- /dev/null +++ b/vendor/alibabacloud/client/src/Encode.php @@ -0,0 +1,64 @@ +data = $data; + } + + /** + * @return bool|string + */ + public function toString() + { + $string = ''; + foreach ($this->data as $key => $value) { + $encode = rawurlencode($value); + $string .= "$key=$encode&"; + } + + if (0 < count($this->data)) { + $string = substr($string, 0, -1); + } + + return $string; + } + + /** + * @return $this + */ + public function ksort() + { + ksort($this->data); + + return $this; + } +} diff --git a/vendor/alibabacloud/client/src/Exception/AlibabaCloudException.php b/vendor/alibabacloud/client/src/Exception/AlibabaCloudException.php new file mode 100644 index 0000000..cee21d8 --- /dev/null +++ b/vendor/alibabacloud/client/src/Exception/AlibabaCloudException.php @@ -0,0 +1,70 @@ +errorCode; + } + + /** + * @codeCoverageIgnore + * @deprecated + */ + public function setErrorCode() + { + throw new RuntimeException('deprecated since 2.0.'); + } + + /** + * @return string + */ + public function getErrorMessage() + { + return $this->errorMessage; + } + + /** + * @codeCoverageIgnore + * + * @param $errorMessage + * + * @deprecated + */ + public function setErrorMessage($errorMessage) + { + $this->errorMessage = $errorMessage; + } + + /** + * @codeCoverageIgnore + * @deprecated + */ + public function setErrorType() + { + } +} diff --git a/vendor/alibabacloud/client/src/Exception/ClientException.php b/vendor/alibabacloud/client/src/Exception/ClientException.php new file mode 100644 index 0000000..0877e87 --- /dev/null +++ b/vendor/alibabacloud/client/src/Exception/ClientException.php @@ -0,0 +1,38 @@ +errorMessage = $errorMessage; + $this->errorCode = $errorCode; + } + + /** + * @codeCoverageIgnore + * @deprecated + */ + public function getErrorType() + { + return 'Client'; + } +} diff --git a/vendor/alibabacloud/client/src/Exception/ServerException.php b/vendor/alibabacloud/client/src/Exception/ServerException.php new file mode 100644 index 0000000..37db53e --- /dev/null +++ b/vendor/alibabacloud/client/src/Exception/ServerException.php @@ -0,0 +1,158 @@ +result = $result; + $this->errorMessage = $errorMessage; + $this->errorCode = $errorCode; + $this->resolvePropertiesByReturn(); + $this->distinguishSignatureErrors(); + $this->bodyAsErrorMessage(); + + parent::__construct( + $this->getMessageString(), + $this->result->getStatusCode() + ); + } + + /** + * Resolve the error message based on the return of the server. + * + * @return void + */ + private function resolvePropertiesByReturn() + { + if (isset($this->result['message'])) { + $this->errorMessage = $this->result['message']; + $this->errorCode = $this->result['code']; + } + if (isset($this->result['Message'])) { + $this->errorMessage = $this->result['Message']; + $this->errorCode = $this->result['Code']; + } + if (isset($this->result['errorMsg'])) { + $this->errorMessage = $this->result['errorMsg']; + $this->errorCode = $this->result['errorCode']; + } + if (isset($this->result['requestId'])) { + $this->requestId = $this->result['requestId']; + } + if (isset($this->result['RequestId'])) { + $this->requestId = $this->result['RequestId']; + } + } + + /** + * If the string to be signed are the same with server's, it is considered a credential error. + */ + private function distinguishSignatureErrors() + { + if ($this->result->getRequest() + && Stringy::create($this->errorMessage)->contains($this->result->getRequest()->stringToSign())) { + $this->errorCode = 'InvalidAccessKeySecret'; + $this->errorMessage = 'Specified Access Key Secret is not valid.'; + } + } + + /** + * If the error message matches the default message and + * the server has returned content, use the return content + */ + private function bodyAsErrorMessage() + { + $body = (string)$this->result->getBody(); + if ($this->errorMessage === SDK::RESPONSE_EMPTY && $body) { + $this->errorMessage = $body; + } + } + + /** + * Get standard exception message. + * + * @return string + */ + private function getMessageString() + { + $message = "$this->errorCode: $this->errorMessage RequestId: $this->requestId"; + + if ($this->getResult()->getRequest()) { + $method = $this->getResult()->getRequest()->method; + $uri = (string)$this->getResult()->getRequest()->uri; + $message .= " $method \"$uri\""; + if ($this->result) { + $message .= ' ' . $this->result->getStatusCode(); + } + } + + return $message; + } + + /** + * @return Result + */ + public function getResult() + { + return $this->result; + } + + /** + * @return string + */ + public function getRequestId() + { + return $this->requestId; + } + + /** + * @codeCoverageIgnore + * @deprecated + */ + public function getErrorType() + { + return 'Server'; + } + + /** + * @codeCoverageIgnore + * @deprecated + */ + public function getHttpStatus() + { + return $this->getResult()->getStatusCode(); + } +} diff --git a/vendor/alibabacloud/client/src/Filter/ApiFilter.php b/vendor/alibabacloud/client/src/Filter/ApiFilter.php new file mode 100644 index 0000000..c12327f --- /dev/null +++ b/vendor/alibabacloud/client/src/Filter/ApiFilter.php @@ -0,0 +1,245 @@ +endsWith(DIRECTORY_SEPARATOR)) { + $dir .= DIRECTORY_SEPARATOR; + } + + if (0 === strpos($filename, $dir)) { + return true; + } + } + + return false; +} + +/** + * @return bool + */ +function isWindows() +{ + return PATH_SEPARATOR === ';'; +} + +/** + * @return CLImate + */ +function cliMate() +{ + return new CLImate(); +} + +/** + * @param string $string + * @param string|null $flank + * @param string|null $char + * @param int|null $length + * + * @return void + */ +function backgroundRed($string, $flank = null, $char = null, $length = null) +{ + cliMate()->br(); + if ($flank !== null) { + cliMate()->backgroundRed()->flank($flank, $char, $length); + cliMate()->br(); + } + cliMate()->backgroundRed($string); + cliMate()->br(); +} + +/** + * @param string $string + * @param string|null $flank + * @param string|null $char + * @param int|null $length + * + * @return void + */ +function backgroundGreen($string, $flank = null, $char = null, $length = null) +{ + cliMate()->br(); + if ($flank !== null) { + cliMate()->backgroundGreen()->flank($flank, $char, $length); + } + cliMate()->backgroundGreen($string); + cliMate()->br(); +} + +/** + * @param string $string + * @param string|null $flank + * @param string|null $char + * @param int|null $length + * + * @return void + */ +function backgroundBlue($string, $flank = null, $char = null, $length = null) +{ + cliMate()->br(); + if ($flank !== null) { + cliMate()->backgroundBlue()->flank($flank, $char, $length); + } + cliMate()->backgroundBlue($string); + cliMate()->br(); +} + +/** + * @param string $string + * @param string|null $flank + * @param string|null $char + * @param int|null $length + * + * @return void + */ +function backgroundMagenta($string, $flank = null, $char = null, $length = null) +{ + cliMate()->br(); + if ($flank !== null) { + cliMate()->backgroundMagenta()->flank($flank, $char, $length); + } + cliMate()->backgroundMagenta($string); + cliMate()->br(); +} + +/** + * @param array $array + */ +function json(array $array) +{ + cliMate()->br(); + cliMate()->backgroundGreen()->json($array); + cliMate()->br(); +} + +/** + * @param array $array + * + * @return void + */ +function redTable($array) +{ + /** + * @noinspection PhpUndefinedMethodInspection + */ + cliMate()->redTable($array); +} + +/** + * @param mixed $result + * @param string $title + * + * @return void + */ +function block($result, $title) +{ + cliMate()->backgroundGreen()->flank($title, '--', 20); + dump($result); +} + +/** + * Gets the value of an environment variable. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ +function env($key, $default = null) +{ + $value = getenv($key); + + if ($value === false) { + return value($default); + } + + if (envSubstr($value)) { + return substr($value, 1, -1); + } + + return envConversion($value); +} + +/** + * @param $value + * + * @return bool|string|null + */ +function envConversion($value) +{ + $key = strtolower($value); + + if ($key === 'null' || $key === '(null)') { + return null; + } + + $list = [ + 'true' => true, + '(true)' => true, + 'false' => false, + '(false)' => false, + 'empty' => '', + '(empty)' => '', + ]; + + return isset($list[$key]) ? $list[$key] : $value; +} + +/** + * @param $key + * + * @return bool|mixed + * @throws ClientException + */ +function envNotEmpty($key) +{ + $value = env($key, false); + if ($value !== false && !$value) { + throw new ClientException( + "Environment variable '$key' cannot be empty", + SDK::INVALID_ARGUMENT + ); + } + if ($value) { + return $value; + } + + return false; +} + +/** + * @param $value + * + * @return bool + */ +function envSubstr($value) +{ + return ($valueLength = strlen($value)) > 1 && strpos($value, '"') === 0 && $value[$valueLength - 1] === '"'; +} + +/** + * Return the default value of the given value. + * + * @param mixed $value + * + * @return mixed + */ +function value($value) +{ + return $value instanceof Closure ? $value() : $value; +} diff --git a/vendor/alibabacloud/client/src/Log/LogFormatter.php b/vendor/alibabacloud/client/src/Log/LogFormatter.php new file mode 100644 index 0000000..a17a1b3 --- /dev/null +++ b/vendor/alibabacloud/client/src/Log/LogFormatter.php @@ -0,0 +1,78 @@ +template = $template; + $timezone = new DateTimeZone(date_default_timezone_get() ?: 'UTC'); + if (PHP_VERSION_ID < 70100) { + self::$ts = DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), $timezone); + } else { + self::$ts = new DateTime(null, $timezone); + } + } + + /** + * 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 + ) { + $this->template = str_replace('{pid}', getmypid(), $this->template); + $this->template = str_replace('{cost}', self::getCost(), $this->template); + $this->template = str_replace('{start_time}', self::$ts->format('Y-m-d H:i:s.u'), $this->template); + + return (new MessageFormatter($this->template))->format($request, $response, $error); + } + + /** + * @return float|mixed + */ + private static function getCost() + { + return microtime(true) - self::$logStartTime; + } +} diff --git a/vendor/alibabacloud/client/src/Profile/DefaultProfile.php b/vendor/alibabacloud/client/src/Profile/DefaultProfile.php new file mode 100644 index 0000000..b1b3707 --- /dev/null +++ b/vendor/alibabacloud/client/src/Profile/DefaultProfile.php @@ -0,0 +1,74 @@ +regionId($regionId); + } + + /** + * @param string $regionId + * @param string $accessKeyId + * @param string $accessKeySecret + * @param string $roleArn + * @param string $roleSessionName + * + * @return Client + * @throws ClientException + */ + public static function getRamRoleArnProfile($regionId, $accessKeyId, $accessKeySecret, $roleArn, $roleSessionName) + { + return AlibabaCloud::ramRoleArnClient($accessKeyId, $accessKeySecret, $roleArn, $roleSessionName) + ->regionId($regionId); + } + + /** + * @param string $regionId + * @param string $roleName + * + * @return Client + * @throws ClientException + */ + public static function getEcsRamRoleProfile($regionId, $roleName) + { + return AlibabaCloud::ecsRamRoleClient($roleName) + ->regionId($regionId); + } + + /** + * @param string $regionId + * @param string $bearerToken + * + * @return Client + * @throws ClientException + */ + public static function getBearerTokenProfile($regionId, $bearerToken) + { + return AlibabaCloud::bearerTokenClient($bearerToken) + ->regionId($regionId); + } +} diff --git a/vendor/alibabacloud/client/src/Regions/EndpointProvider.php b/vendor/alibabacloud/client/src/Regions/EndpointProvider.php new file mode 100644 index 0000000..5e8e555 --- /dev/null +++ b/vendor/alibabacloud/client/src/Regions/EndpointProvider.php @@ -0,0 +1,18 @@ +request = $request; + } + + /** + * @param Request $request + * @param string $domain + * + * @return string + * @throws ClientException + * @throws ServerException + * @deprecated + * @codeCoverageIgnore + */ + public static function findProductDomain(Request $request, $domain = 'location.aliyuncs.com') + { + return self::resolveHost($request, $domain); + } + + /** + * @param $regionId + * @param $product + * @param $domain + * + * @throws ClientException + * @deprecated + * @codeCoverageIgnore + */ + public static function addEndPoint($regionId, $product, $domain) + { + self::addHost($product, $domain, $regionId); + } + + + /** + * @param Request $request + * @param string $domain + * + * @return string + * @throws ClientException + * @throws ServerException + */ + public static function resolveHost(Request $request, $domain = 'location.aliyuncs.com') + { + $locationService = new static($request); + $product = $locationService->request->product; + $regionId = $locationService->request->realRegionId(); + + if (!isset(self::$hosts[$product][$regionId])) { + self::$hosts[$product][$regionId] = self::getResult($locationService, $domain); + } + + return self::$hosts[$product][$regionId]; + } + + /** + * @param static $locationService + * @param string $domain + * + * @return string + * @throws ClientException + * @throws ServerException + */ + private static function getResult($locationService, $domain) + { + $locationRequest = new LocationServiceRequest($locationService->request, $domain); + + $result = $locationRequest->request(); + + if (!isset($result['Endpoints']['Endpoint'][0]['Endpoint'])) { + throw new ClientException( + 'Not found Region ID in ' . $domain, + SDK::INVALID_REGION_ID + ); + } + + return $result['Endpoints']['Endpoint'][0]['Endpoint']; + } + + /** + * @param string $product + * @param string $host + * @param string $regionId + * + * @throws ClientException + */ + public static function addHost($product, $host, $regionId = self::GLOBAL_REGION) + { + ApiFilter::product($product); + + HttpFilter::host($host); + + ClientFilter::regionId($regionId); + + self::$hosts[$product][$regionId] = $host; + } + + /** + * Update endpoints from OSS. + * + * @codeCoverageIgnore + * @throws Exception + */ + public static function updateEndpoints() + { + $ossUrl = 'https://openapi-endpoints.oss-cn-hangzhou.aliyuncs.com/endpoints.json'; + $json = \file_get_contents($ossUrl); + $list = \json_decode($json, true); + + foreach ($list['endpoints'] as $endpoint) { + Config::set( + "endpoints.{$endpoint['service']}.{$endpoint['regionid']}", + \strtolower($endpoint['endpoint']) + ); + } + } +} diff --git a/vendor/alibabacloud/client/src/Regions/LocationServiceRequest.php b/vendor/alibabacloud/client/src/Regions/LocationServiceRequest.php new file mode 100644 index 0000000..94a267d --- /dev/null +++ b/vendor/alibabacloud/client/src/Regions/LocationServiceRequest.php @@ -0,0 +1,46 @@ +product('Location'); + $this->version('2015-06-12'); + $this->action('DescribeEndpoints'); + $this->regionId('cn-hangzhou'); + $this->format('JSON'); + $this->options['query']['Id'] = $request->realRegionId(); + $this->options['query']['ServiceCode'] = $request->serviceCode; + $this->options['query']['Type'] = $request->endpointType; + $this->client($request->client); + $this->host($domain); + if (isset($request->options['timeout'])) { + $this->timeout($request->options['timeout']); + } + + if (isset($request->options['connect_timeout'])) { + $this->connectTimeout($request->options['connect_timeout']); + } + } +} diff --git a/vendor/alibabacloud/client/src/Release.php b/vendor/alibabacloud/client/src/Release.php new file mode 100644 index 0000000..0be9c1b --- /dev/null +++ b/vendor/alibabacloud/client/src/Release.php @@ -0,0 +1,112 @@ +getArguments(); + if (count($arguments) <= 1) { + echo 'Missing ChangeLog'; + + return; + } + self::updateChangelogFile($arguments[0], $arguments[1]); + self::changeVersionInCode($arguments[0]); + } + + /** + * @param $version + * @param $changeLog + */ + private static function updateChangelogFile($version, $changeLog) + { + $content = preg_replace( + '/# CHANGELOG/', + '# CHANGELOG' + . "\n" + . "\n" + . "## $version - " . date('Y-m-d') + . self::log($changeLog), + self::getChangeLogContent() + ); + + file_put_contents(self::getChangeLogFile(), $content); + } + + /** + * @param $changeLog + * + * @return string + */ + private static function log($changeLog) + { + $logs = explode('|', $changeLog); + $string = "\n"; + foreach ($logs as $log) { + if ($log) { + $string .= "- $log." . "\n"; + } + } + + return $string; + } + + /** + * @return string + */ + private static function getChangeLogContent() + { + return file_get_contents(self::getChangeLogFile()); + } + + /** + * @return string + */ + private static function getChangeLogFile() + { + return __DIR__ . '/../CHANGELOG.md'; + } + + /** + * @param $version + */ + private static function changeVersionInCode($version) + { + $content = preg_replace( + "/const VERSION = \'(.*)\';/", + "const VERSION = '" . $version . "';", + self::getCodeContent() + ); + + file_put_contents(self::getCodeFile(), $content); + } + + /** + * @return string + */ + private static function getCodeContent() + { + return file_get_contents(self::getCodeFile()); + } + + /** + * @return string + */ + private static function getCodeFile() + { + return __DIR__ . '/AlibabaCloud.php'; + } +} diff --git a/vendor/alibabacloud/client/src/Request/Request.php b/vendor/alibabacloud/client/src/Request/Request.php new file mode 100644 index 0000000..94ec12c --- /dev/null +++ b/vendor/alibabacloud/client/src/Request/Request.php @@ -0,0 +1,445 @@ +client = CredentialsProvider::getDefaultName(); + $this->uri = new Uri(); + $this->uri = $this->uri->withScheme($this->scheme); + $this->options['http_errors'] = false; + $this->options['connect_timeout'] = self::CONNECT_TIMEOUT; + $this->options['timeout'] = self::TIMEOUT; + + // Turn on debug mode based on environment variable. + if (strtolower(\AlibabaCloud\Client\env('DEBUG')) === 'sdk') { + $this->options['debug'] = true; + } + + // Rewrite configuration if the user has a configuration. + if ($options !== []) { + $this->options($options); + } + } + + /** + * @param string $name + * @param string $value + * + * @return $this + * @throws ClientException + */ + public function appendUserAgent($name, $value) + { + $filter_name = Filter::name($name); + + if (!UserAgent::isGuarded($filter_name)) { + $this->userAgent[$filter_name] = Filter::value($value); + } + + return $this; + } + + /** + * @param array $userAgent + * + * @return $this + */ + public function withUserAgent(array $userAgent) + { + $this->userAgent = UserAgent::clean($userAgent); + + return $this; + } + + /** + * Set Accept format. + * + * @param string $format + * + * @return $this + * @throws ClientException + */ + public function format($format) + { + $this->format = ApiFilter::format($format); + + return $this; + } + + /** + * @param $contentType + * + * @return $this + * @throws ClientException + */ + public function contentType($contentType) + { + $this->options['headers']['Content-Type'] = HttpFilter::contentType($contentType); + + return $this; + } + + /** + * @param string $accept + * + * @return $this + * @throws ClientException + */ + public function accept($accept) + { + $this->options['headers']['Accept'] = HttpFilter::accept($accept); + + return $this; + } + + /** + * Set the request body. + * + * @param string $body + * + * @return $this + * @throws ClientException + */ + public function body($body) + { + $this->options['body'] = HttpFilter::body($body); + + return $this; + } + + /** + * Set the json as body. + * + * @param array|object $content + * + * @return $this + * @throws ClientException + */ + public function jsonBody($content) + { + if (!\is_array($content) && !\is_object($content)) { + throw new ClientException( + 'jsonBody only accepts an array or object', + SDK::INVALID_ARGUMENT + ); + } + + return $this->body(\json_encode($content)); + } + + /** + * Set the request scheme. + * + * @param string $scheme + * + * @return $this + * @throws ClientException + */ + public function scheme($scheme) + { + $this->scheme = HttpFilter::scheme($scheme); + $this->uri = $this->uri->withScheme($scheme); + + return $this; + } + + /** + * Set the request host. + * + * @param string $host + * + * @return $this + * @throws ClientException + */ + public function host($host) + { + $this->uri = $this->uri->withHost(HttpFilter::host($host)); + + return $this; + } + + /** + * @param string $method + * + * @return $this + * @throws ClientException + */ + public function method($method) + { + $this->method = HttpFilter::method($method); + + return $this; + } + + /** + * @param string $clientName + * + * @return $this + * @throws ClientException + */ + public function client($clientName) + { + $this->client = ClientFilter::clientName($clientName); + + return $this; + } + + /** + * @return bool + * @throws ClientException + */ + public function isDebug() + { + if (isset($this->options['debug'])) { + return $this->options['debug'] === true; + } + + if (isset($this->httpClient()->options['debug'])) { + return $this->httpClient()->options['debug'] === true; + } + + return false; + } + + /** + * @throws ClientException + * @throws ServerException + */ + public function resolveOption() + { + $this->options['headers']['User-Agent'] = UserAgent::toString($this->userAgent); + + $this->cleanQuery(); + $this->cleanFormParams(); + $this->resolveHost(); + $this->resolveParameter(); + + if (isset($this->options['form_params'])) { + $this->options['form_params'] = \GuzzleHttp\Psr7\parse_query( + Encode::create($this->options['form_params'])->toString() + ); + } + + $this->mergeOptionsIntoClient(); + } + + /** + * @return Result + * @throws ClientException + * @throws ServerException + */ + public function request() + { + $this->resolveOption(); + $result = $this->response(); + + if ($this->shouldServerRetry($result)) { + return $this->request(); + } + + if (!$result->isSuccess()) { + throw new ServerException($result); + } + + return $result; + } + + /*** + * @return PromiseInterface + * @throws Exception + */ + public function requestAsync() + { + $this->resolveOption(); + + return self::createClient($this)->requestAsync( + $this->method, + (string)$this->uri, + $this->options + ); + } + + /** + * @param Request $request + * + * @return Client + * @throws Exception + */ + public static function createClient(Request $request = null) + { + if (AlibabaCloud::hasMock()) { + $stack = HandlerStack::create(AlibabaCloud::getMock()); + } else { + $stack = HandlerStack::create(); + } + + if (AlibabaCloud::isRememberHistory()) { + $stack->push(Middleware::history(AlibabaCloud::referenceHistory())); + } + + if (AlibabaCloud::getLogger()) { + $stack->push(Middleware::log( + AlibabaCloud::getLogger(), + new LogFormatter(AlibabaCloud::getLogFormat()) + )); + } + + $stack->push(Middleware::mapResponse(static function (ResponseInterface $response) use ($request) { + return new Result($response, $request); + })); + + self::$config['handler'] = $stack; + + return new Client(self::$config); + } + + /** + * @throws ClientException + * @throws Exception + */ + private function response() + { + try { + return self::createClient($this)->request( + $this->method, + (string)$this->uri, + $this->options + ); + } catch (GuzzleException $exception) { + if ($this->shouldClientRetry($exception)) { + return $this->response(); + } + throw new ClientException( + $exception->getMessage(), + SDK::SERVER_UNREACHABLE, + $exception + ); + } + } + + /** + * Remove redundant Query + * + * @codeCoverageIgnore + */ + private function cleanQuery() + { + if (isset($this->options['query']) && $this->options['query'] === []) { + unset($this->options['query']); + } + } + + /** + * Remove redundant Headers + * + * @codeCoverageIgnore + */ + private function cleanFormParams() + { + if (isset($this->options['form_params']) && $this->options['form_params'] === []) { + unset($this->options['form_params']); + } + } +} diff --git a/vendor/alibabacloud/client/src/Request/RoaRequest.php b/vendor/alibabacloud/client/src/Request/RoaRequest.php new file mode 100644 index 0000000..a6af50e --- /dev/null +++ b/vendor/alibabacloud/client/src/Request/RoaRequest.php @@ -0,0 +1,335 @@ +resolveQuery(); + $this->resolveHeaders(); + $this->resolveBody(); + $this->resolveUri(); + $this->resolveSignature(); + } + + private function resolveQuery() + { + if (!isset($this->options['query']['Version'])) { + $this->options['query']['Version'] = $this->version; + } + } + + private function resolveBody() + { + // If the body has already been specified, it will not be resolved. + if (isset($this->options['body'])) { + return; + } + + if (!isset($this->options['form_params'])) { + return; + } + + // Merge data, compatible with parameters set from constructor. + $params = Arrays::merge( + [ + $this->data, + $this->options['form_params'] + ] + ); + + $this->encodeBody($params); + + unset($this->options['form_params']); + } + + /** + * Determine the body format based on the Content-Type and calculate the MD5 value. + * + * @param array $params + */ + private function encodeBody(array $params) + { + $stringy = Stringy::create($this->options['headers']['Content-Type']); + + if ($stringy->contains('application/json', false)) { + $this->options['body'] = json_encode($params); + $this->options['headers']['Content-MD5'] = base64_encode(md5($this->options['body'], true)); + + return; + } + + $this->options['body'] = Encode::create($params)->ksort()->toString(); + $this->options['headers']['Content-MD5'] = base64_encode(md5($this->options['body'], true)); + $this->options['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; + } + + /** + * @throws ClientException + * @throws ServerException + * @throws Exception + */ + private function resolveHeaders() + { + $this->options['headers']['x-acs-version'] = $this->version; + $this->options['headers']['x-acs-region-id'] = $this->realRegionId(); + $this->options['headers']['Date'] = gmdate($this->dateTimeFormat); + + $signature = $this->httpClient()->getSignature(); + $this->options['headers']['x-acs-signature-method'] = $signature->getMethod(); + $this->options['headers']['x-acs-signature-nonce'] = Sign::uuid($this->product . $this->realRegionId()); + $this->options['headers']['x-acs-signature-version'] = $signature->getVersion(); + if ($signature->getType()) { + $this->options['headers']['x-acs-signature-type'] = $signature->getType(); + } + + $this->resolveAccept(); + $this->resolveContentType(); + $this->resolveSecurityToken(); + $this->resolveBearerToken(); + } + + /** + * @throws ClientException + * @throws Exception + */ + private function resolveSignature() + { + $this->options['headers']['Authorization'] = $this->signature(); + } + + /** + * If accept is not specified, it is determined by format. + */ + private function resolveAccept() + { + if (!isset($this->options['headers']['Accept'])) { + $this->options['headers']['Accept'] = Accept::create($this->format)->toString(); + } + } + + /** + * If the Content-Type is not specified, it is determined according to accept. + */ + private function resolveContentType() + { + if (!isset($this->options['headers']['Content-Type'])) { + $this->options['headers']['Content-Type'] = "{$this->options['headers']['Accept']}; charset=utf-8"; + } + } + + /** + * @throws ClientException + * @throws ServerException + */ + private function resolveSecurityToken() + { + if (!$this->credential() instanceof StsCredential) { + return; + } + + if (!$this->credential()->getSecurityToken()) { + return; + } + + $this->options['headers']['x-acs-security-token'] = $this->credential()->getSecurityToken(); + } + + /** + * @throws ClientException + * @throws ServerException + */ + private function resolveBearerToken() + { + if ($this->credential() instanceof BearerTokenCredential) { + $this->options['headers']['x-acs-bearer-token'] = $this->credential()->getBearerToken(); + } + } + + /** + * Sign the request message. + * + * @return string + * @throws ClientException + * @throws ServerException + */ + private function signature() + { + /** + * @var AccessKeyCredential $credential + */ + $credential = $this->credential(); + $access_key_id = $credential->getAccessKeyId(); + $signature = $this->httpClient() + ->getSignature() + ->sign( + $this->stringToSign(), + $credential->getAccessKeySecret() + ); + + return "acs $access_key_id:$signature"; + } + + /** + * @return void + */ + private function resolveUri() + { + $path = Path::assign($this->pathPattern, $this->pathParameters); + + $this->uri = $this->uri->withPath($path) + ->withQuery( + $this->queryString() + ); + } + + /** + * @return string + */ + public function stringToSign() + { + $request = new \GuzzleHttp\Psr7\Request( + $this->method, + $this->uri, + $this->options['headers'] + ); + + return Sign::roaString($request); + } + + /** + * @return bool|string + */ + private function queryString() + { + $query = isset($this->options['query']) + ? $this->options['query'] + : []; + + return Encode::create($query)->ksort()->toString(); + } + + /** + * Set path parameter by name. + * + * @param string $name + * @param string $value + * + * @return RoaRequest + * @throws ClientException + */ + public function pathParameter($name, $value) + { + Filter::name($name); + + if ($value === '') { + throw new ClientException( + 'Value cannot be empty', + SDK::INVALID_ARGUMENT + ); + } + + $this->pathParameters[$name] = $value; + + return $this; + } + + /** + * Set path pattern. + * + * @param string $pattern + * + * @return self + * @throws ClientException + */ + public function pathPattern($pattern) + { + ApiFilter::pattern($pattern); + + $this->pathPattern = $pattern; + + return $this; + } + + /** + * Magic method for set or get request parameters. + * + * @param string $name + * @param mixed $arguments + * + * @return $this + */ + public function __call($name, $arguments) + { + if (strncmp($name, 'get', 3) === 0) { + $parameter_name = \mb_strcut($name, 3); + + return $this->__get($parameter_name); + } + + if (strncmp($name, 'with', 4) === 0) { + $parameter_name = \mb_strcut($name, 4); + $this->__set($parameter_name, $arguments[0]); + $this->pathParameters[$parameter_name] = $arguments[0]; + + return $this; + } + + if (strncmp($name, 'set', 3) === 0) { + $parameter_name = \mb_strcut($name, 3); + $with_method = "with$parameter_name"; + + throw new RuntimeException("Please use $with_method instead of $name"); + } + + throw new RuntimeException('Call to undefined method ' . __CLASS__ . '::' . $name . '()'); + } +} diff --git a/vendor/alibabacloud/client/src/Request/RpcRequest.php b/vendor/alibabacloud/client/src/Request/RpcRequest.php new file mode 100644 index 0000000..b9d7369 --- /dev/null +++ b/vendor/alibabacloud/client/src/Request/RpcRequest.php @@ -0,0 +1,203 @@ +resolveBoolInParameters(); + $this->resolveCommonParameters(); + $this->repositionParameters(); + } + + /** + * Convert a Boolean value to a string + */ + private function resolveBoolInParameters() + { + if (isset($this->options['query'])) { + $this->options['query'] = array_map( + static function ($value) { + return self::boolToString($value); + }, + $this->options['query'] + ); + } + } + + /** + * Convert a Boolean value to a string. + * + * @param bool|string $value + * + * @return string + */ + public static function boolToString($value) + { + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + + return $value; + } + + /** + * Resolve Common Parameters. + * + * @throws ClientException + * @throws Exception + */ + private function resolveCommonParameters() + { + $signature = $this->httpClient()->getSignature(); + $this->options['query']['RegionId'] = $this->realRegionId(); + $this->options['query']['Format'] = $this->format; + $this->options['query']['SignatureMethod'] = $signature->getMethod(); + $this->options['query']['SignatureVersion'] = $signature->getVersion(); + $this->options['query']['SignatureNonce'] = Sign::uuid($this->product . $this->realRegionId()); + $this->options['query']['Timestamp'] = gmdate($this->dateTimeFormat); + $this->options['query']['Action'] = $this->action; + if ($this->credential()->getAccessKeyId()) { + $this->options['query']['AccessKeyId'] = $this->credential()->getAccessKeyId(); + } + if ($signature->getType()) { + $this->options['query']['SignatureType'] = $signature->getType(); + } + if (!isset($this->options['query']['Version'])) { + $this->options['query']['Version'] = $this->version; + } + $this->resolveSecurityToken(); + $this->resolveBearerToken(); + $this->options['query']['Signature'] = $this->signature(); + } + + /** + * @throws ClientException + * @throws ServerException + */ + private function resolveSecurityToken() + { + if (!$this->credential() instanceof StsCredential) { + return; + } + + if (!$this->credential()->getSecurityToken()) { + return; + } + + $this->options['query']['SecurityToken'] = $this->credential()->getSecurityToken(); + } + + /** + * @throws ClientException + * @throws ServerException + */ + private function resolveBearerToken() + { + if ($this->credential() instanceof BearerTokenCredential) { + $this->options['query']['BearerToken'] = $this->credential()->getBearerToken(); + } + } + + /** + * Sign the parameters. + * + * @return mixed + * @throws ClientException + * @throws ServerException + */ + private function signature() + { + return $this->httpClient() + ->getSignature() + ->sign( + $this->stringToSign(), + $this->credential()->getAccessKeySecret() . '&' + ); + } + + /** + * @return string + */ + public function stringToSign() + { + $query = isset($this->options['query']) ? $this->options['query'] : []; + $form_params = isset($this->options['form_params']) ? $this->options['form_params'] : []; + $parameters = Arrays::merge([$query, $form_params]); + + return Sign::rpcString($this->method, $parameters); + } + + /** + * Adjust parameter position + */ + private function repositionParameters() + { + if ($this->method === 'POST' || $this->method === 'PUT') { + foreach ($this->options['query'] as $api_key => $api_value) { + $this->options['form_params'][$api_key] = $api_value; + } + unset($this->options['query']); + } + } + + /** + * Magic method for set or get request parameters. + * + * @param string $name + * @param mixed $arguments + * + * @return $this + */ + public function __call($name, $arguments) + { + if (strncmp($name, 'get', 3) === 0) { + $parameter_name = \mb_strcut($name, 3); + + return $this->__get($parameter_name); + } + + if (strncmp($name, 'with', 4) === 0) { + $parameter_name = \mb_strcut($name, 4); + $this->__set($parameter_name, $arguments[0]); + $this->options['query'][$parameter_name] = $arguments[0]; + + return $this; + } + + if (strncmp($name, 'set', 3) === 0) { + $parameter_name = \mb_strcut($name, 3); + $with_method = "with$parameter_name"; + + throw new RuntimeException("Please use $with_method instead of $name"); + } + + throw new RuntimeException('Call to undefined method ' . __CLASS__ . '::' . $name . '()'); + } +} diff --git a/vendor/alibabacloud/client/src/Request/Traits/AcsTrait.php b/vendor/alibabacloud/client/src/Request/Traits/AcsTrait.php new file mode 100644 index 0000000..3b0aca0 --- /dev/null +++ b/vendor/alibabacloud/client/src/Request/Traits/AcsTrait.php @@ -0,0 +1,239 @@ +action = ApiFilter::action($action); + + return $this; + } + + /** + * @codeCoverageIgnore + * + * @param string $endpointSuffix + * + * @return AcsTrait + * @throws ClientException + */ + public function endpointSuffix($endpointSuffix) + { + $this->endpointSuffix = ApiFilter::endpointSuffix($endpointSuffix); + + return $this; + } + + /** + * @param string $network + */ + public function network($network) + { + $this->network = ApiFilter::network($network); + + return $this; + } + + /** + * @param string $version + * + * @return $this + * @throws ClientException + */ + public function version($version) + { + $this->version = ApiFilter::version($version); + + return $this; + } + + /** + * @param string $product + * + * @return $this + * @throws ClientException + */ + public function product($product) + { + $this->product = ApiFilter::product($product); + + return $this; + } + + /** + * @param string $endpointType + * + * @return $this + * @throws ClientException + */ + public function endpointType($endpointType) + { + $this->endpointType = ApiFilter::endpointType($endpointType); + + return $this; + } + + /** + * @param string $serviceCode + * + * @return $this + * @throws ClientException + */ + public function serviceCode($serviceCode) + { + $this->serviceCode = ApiFilter::serviceCode($serviceCode); + + return $this; + } + + /** + * Resolve Host. + * + * @throws ClientException + * @throws ServerException + */ + public function resolveHost() + { + // Return if specified + if ($this->uri->getHost() !== 'localhost') { + return; + } + + $region_id = $this->realRegionId(); + $host = ''; + + $this->resolveHostWays($host, $region_id); + + if (!$host) { + throw new ClientException( + "No host found for {$this->product} in the {$region_id}, you can specify host by host() method. " . + 'Like $request->host(\'xxx.xxx.aliyuncs.com\')', + SDK::HOST_NOT_FOUND + ); + } + + $this->uri = $this->uri->withHost($host); + } + + /** + * @param string $host + * @param string $region_id + * + * @throws ClientException + * @throws ServerException + */ + private function resolveHostWays(&$host, $region_id) + { + $host = AlibabaCloud::resolveHostByStatic($this->product, $region_id); + + // 1. Find host by map. + if (!$host && $this->network === 'public' && isset($this->endpointMap[$region_id])) { + $host = $this->endpointMap[$region_id]; + } + + // 2. Find host by rules. + if (!$host && $this->endpointRegional !== null) { + $host = AlibabaCloud::resolveHostByRule($this); + } + + // 3. Find in the local array file. + if (!$host) { + $host = AlibabaCloud::resolveHost($this->product, $region_id); + } + + // 4. Find in the Location service. + if (!$host && $this->serviceCode) { + $host = LocationService::resolveHost($this); + } + } + + /** + * @return string + * @throws ClientException + */ + public function realRegionId() + { + if ($this->regionId !== null) { + return $this->regionId; + } + + if ($this->httpClient()->regionId !== null) { + return $this->httpClient()->regionId; + } + + if (AlibabaCloud::getDefaultRegionId() !== null) { + return AlibabaCloud::getDefaultRegionId(); + } + + throw new ClientException("Missing required 'RegionId' for Request", SDK::INVALID_REGION_ID); + } +} diff --git a/vendor/alibabacloud/client/src/Request/Traits/ClientTrait.php b/vendor/alibabacloud/client/src/Request/Traits/ClientTrait.php new file mode 100644 index 0000000..37a2d22 --- /dev/null +++ b/vendor/alibabacloud/client/src/Request/Traits/ClientTrait.php @@ -0,0 +1,98 @@ +httpClient()->getCredential(); + } + + $timeout = isset($this->options['timeout']) + ? $this->options['timeout'] + : Request::TIMEOUT; + + $connectTimeout = isset($this->options['connect_timeout']) + ? $this->options['connect_timeout'] + : Request::CONNECT_TIMEOUT; + + return $this->httpClient()->getSessionCredential($timeout, $connectTimeout); + } + + /** + * Get the client based on the request's settings. + * + * @return Client + * @throws ClientException + */ + public function httpClient() + { + if (!AlibabaCloud::all()) { + if (CredentialsProvider::hasCustomChain()) { + CredentialsProvider::customProvider($this->client); + } else { + CredentialsProvider::defaultProvider($this->client); + } + } + + return AlibabaCloud::get($this->client); + } + + /** + * Merged with the client's options, the same name will be overwritten. + * + * @throws ClientException + */ + public function mergeOptionsIntoClient() + { + $this->options = Arrays::merge( + [ + $this->httpClient()->options, + $this->options + ] + ); + } +} diff --git a/vendor/alibabacloud/client/src/Request/Traits/DeprecatedRoaTrait.php b/vendor/alibabacloud/client/src/Request/Traits/DeprecatedRoaTrait.php new file mode 100644 index 0000000..ee64e52 --- /dev/null +++ b/vendor/alibabacloud/client/src/Request/Traits/DeprecatedRoaTrait.php @@ -0,0 +1,55 @@ +pathParameter($name, $value); + } + + /** + * @param $pathPattern + * + * @return $this + * @deprecated + * @codeCoverageIgnore + */ + public function setUriPattern($pathPattern) + { + return $this->pathPattern($pathPattern); + } + + /** + * @return string + * @deprecated + * @codeCoverageIgnore + */ + public function getUriPattern() + { + return $this->pathPattern; + } + + /** + * @return array + * @deprecated + * @codeCoverageIgnore + */ + public function getPathParameters() + { + return $this->pathParameters; + } +} diff --git a/vendor/alibabacloud/client/src/Request/Traits/DeprecatedTrait.php b/vendor/alibabacloud/client/src/Request/Traits/DeprecatedTrait.php new file mode 100644 index 0000000..406e6ed --- /dev/null +++ b/vendor/alibabacloud/client/src/Request/Traits/DeprecatedTrait.php @@ -0,0 +1,246 @@ +body($content); + } + + /** + * @param $method + * + * @return $this + * @throws ClientException + * @deprecated + * @codeCoverageIgnore + */ + public function setMethod($method) + { + return $this->method($method); + } + + /** + * @param $scheme + * + * @return $this + * @throws ClientException + * @deprecated + * @codeCoverageIgnore + */ + public function setProtocol($scheme) + { + return $this->scheme($scheme); + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getProtocolType() + { + return $this->uri->getScheme(); + } + + /** + * @param $scheme + * + * @return $this + * @throws ClientException + * @deprecated + * @codeCoverageIgnore + */ + public function setProtocolType($scheme) + { + return $this->scheme($scheme); + } + + /** + * @param $actionName + * + * @return $this + * @throws ClientException + * @deprecated + * @codeCoverageIgnore + */ + public function setActionName($actionName) + { + return $this->action($actionName); + } + + /** + * @param $format + * + * @return $this + * @throws ClientException + * @deprecated + * @codeCoverageIgnore + */ + public function setAcceptFormat($format) + { + return $this->format($format); + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getProtocol() + { + return $this->uri->getScheme(); + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getContent() + { + return isset($this->options['body']) + ? $this->options['body'] + : null; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getMethod() + { + return $this->method; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getHeaders() + { + return isset($this->options['headers']) + ? $this->options['headers'] + : []; + } + + /** + * @param $headerKey + * @param $headerValue + * + * @return $this + * @deprecated + * @codeCoverageIgnore + */ + public function addHeader($headerKey, $headerValue) + { + $this->options['headers'][$headerKey] = $headerValue; + + return $this; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getQueryParameters() + { + return isset($this->options['query']) + ? $this->options['query'] + : []; + } + + /** + * @param $name + * @param $value + * + * @return $this + * @deprecated + * @codeCoverageIgnore + */ + public function setQueryParameters($name, $value) + { + $this->options['query'][$name] = $value; + + return $this; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getDomainParameter() + { + return isset($this->options['form_params']) + ? $this->options['form_params'] + : []; + } + + /** + * @param $name + * @param $value + * + * @return $this + * @deprecated + * @codeCoverageIgnore + */ + public function putDomainParameters($name, $value) + { + $this->options['form_params'][$name] = $value; + + return $this; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getActionName() + { + return $this->action; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getAcceptFormat() + { + return $this->format; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getLocationEndpointType() + { + return $this->endpointType; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getLocationServiceCode() + { + return $this->serviceCode; + } +} diff --git a/vendor/alibabacloud/client/src/Request/Traits/RetryTrait.php b/vendor/alibabacloud/client/src/Request/Traits/RetryTrait.php new file mode 100644 index 0000000..ff35b64 --- /dev/null +++ b/vendor/alibabacloud/client/src/Request/Traits/RetryTrait.php @@ -0,0 +1,149 @@ +serverRetry = ClientFilter::retry($times); + $this->serverRetryStrings = $strings; + $this->serverRetryStatusCodes = $statusCodes; + + return $this; + } + + /** + * @param int $times + * @param array $strings + * @param array $codes + * + * @return $this + * @throws ClientException + */ + public function retryByClient($times, array $strings, array $codes = []) + { + $this->clientRetry = ClientFilter::retry($times); + $this->clientRetryStrings = $strings; + $this->clientRetryStatusCodes = $codes; + + return $this; + } + + /** + * @param Result $result + * + * @return bool + */ + private function shouldServerRetry(Result $result) + { + if ($this->serverRetry <= 0) { + return false; + } + + if (in_array($result->getStatusCode(), $this->serverRetryStatusCodes)) { + $this->serverRetry--; + + return true; + } + + foreach ($this->serverRetryStrings as $message) { + if (Stringy::create($result->getBody())->contains($message)) { + $this->serverRetry--; + + return true; + } + } + + return false; + } + + /** + * @param Exception $exception + * + * @return bool + */ + private function shouldClientRetry(Exception $exception) + { + if ($this->clientRetry <= 0) { + return false; + } + + if (in_array($exception->getCode(), $this->clientRetryStatusCodes, true)) { + $this->clientRetry--; + + return true; + } + + foreach ($this->clientRetryStrings as $message) { + if (Stringy::create($exception->getMessage())->contains($message)) { + $this->clientRetry--; + + return true; + } + } + + return false; + } +} diff --git a/vendor/alibabacloud/client/src/Request/UserAgent.php b/vendor/alibabacloud/client/src/Request/UserAgent.php new file mode 100644 index 0000000..beb3283 --- /dev/null +++ b/vendor/alibabacloud/client/src/Request/UserAgent.php @@ -0,0 +1,142 @@ + $value) { + if ($value === null) { + $newUserAgent[] = $key; + continue; + } + $newUserAgent[] = "$key/$value"; + } + + return $userAgent . \implode(' ', $newUserAgent); + } + + /** + * UserAgent constructor. + */ + private static function defaultFields() + { + if (self::$userAgent === []) { + self::$userAgent = [ + 'Client' => AlibabaCloud::VERSION, + 'PHP' => \PHP_VERSION, + ]; + } + } + + /** + * @param array $append + * + * @return array + */ + public static function clean(array $append) + { + foreach ($append as $key => $value) { + if (self::isGuarded($key)) { + unset($append[$key]); + continue; + } + } + + return $append; + } + + /** + * @param $name + * + * @return bool + */ + public static function isGuarded($name) + { + return in_array(strtolower($name), self::$guard, true); + } + + /** + * set User Agent of Alibaba Cloud. + * + * @param string $name + * @param string $value + * + * @throws ClientException + */ + public static function append($name, $value) + { + Filter::name($name); + Filter::value($value); + + self::defaultFields(); + + if (!self::isGuarded($name)) { + self::$userAgent[$name] = $value; + } + } + + /** + * @param array $userAgent + */ + public static function with(array $userAgent) + { + self::$userAgent = self::clean($userAgent); + } + + /** + * Clear all of the User Agent. + */ + public static function clear() + { + self::$userAgent = []; + } +} diff --git a/vendor/alibabacloud/client/src/Resolver/ActionResolverTrait.php b/vendor/alibabacloud/client/src/Resolver/ActionResolverTrait.php new file mode 100644 index 0000000..f375567 --- /dev/null +++ b/vendor/alibabacloud/client/src/Resolver/ActionResolverTrait.php @@ -0,0 +1,50 @@ +action) { + $array = explode('\\', get_class($this)); + $this->action = array_pop($array); + } + } + + /** + * Append SDK version into User-Agent + * + * @throws ClientException + * @throws ReflectionException + */ + private function appendSdkUA() + { + if (!(new ReflectionClass(AlibabaCloud::class))->hasMethod('appendUserAgent')) { + return; + } + + if (!class_exists('AlibabaCloud\Release')) { + return; + } + + AlibabaCloud::appendUserAgent('SDK', \AlibabaCloud\Release::VERSION); + } +} diff --git a/vendor/alibabacloud/client/src/Resolver/ApiResolver.php b/vendor/alibabacloud/client/src/Resolver/ApiResolver.php new file mode 100644 index 0000000..7a2bc2b --- /dev/null +++ b/vendor/alibabacloud/client/src/Resolver/ApiResolver.php @@ -0,0 +1,113 @@ +__call($name, $arguments); + } + + /** + * @param $api + * @param $arguments + * + * @return mixed + * @throws ClientException + */ + public function __call($api, $arguments) + { + $product_name = $this->getProductName(); + $class = $this->getNamespace() . '\\' . \ucfirst($api); + + if (\class_exists($class)) { + if (isset($arguments[0])) { + return $this->warpEndpoint(new $class($arguments[0])); + } + + return $this->warpEndpoint(new $class()); + } + + throw new ClientException( + "{$product_name} contains no $api", + 'SDK.ApiNotFound' + ); + } + + /** + * @param Request $request + * + * @return Request + */ + public function warpEndpoint(Request $request) + { + $reflect = new ReflectionObject($request); + $product_dir = dirname(dirname($reflect->getFileName())); + $endpoints_json = "$product_dir/endpoints.json"; + if (file_exists($endpoints_json)) { + $endpoints = json_decode(file_get_contents($endpoints_json), true); + if (isset($endpoints['endpoint_map'])) { + $request->endpointMap = $endpoints['endpoint_map']; + } + if (isset($endpoints['endpoint_regional'])) { + $request->endpointRegional = $endpoints['endpoint_regional']; + } + } + + return $request; + } + + /** + * @return mixed + * @throws ClientException + */ + private function getProductName() + { + $array = \explode('\\', \get_class($this)); + if (isset($array[3])) { + return str_replace('ApiResolver', '', $array[3]); + } + throw new ClientException( + 'Service name not found.', + 'SDK.ServiceNotFound' + ); + } + + /** + * @return string + * @throws ClientException + */ + private function getNamespace() + { + $array = \explode('\\', \get_class($this)); + + if (!isset($array[3])) { + throw new ClientException( + 'Get namespace error.', + 'SDK.ParseError' + ); + } + + unset($array[3]); + + return \implode('\\', $array); + } +} diff --git a/vendor/alibabacloud/client/src/Resolver/CallTrait.php b/vendor/alibabacloud/client/src/Resolver/CallTrait.php new file mode 100644 index 0000000..33afdce --- /dev/null +++ b/vendor/alibabacloud/client/src/Resolver/CallTrait.php @@ -0,0 +1,67 @@ +__get($parameter); + } + + if (strncmp($name, 'with', 4) === 0) { + $parameter = \mb_strcut($name, 4); + + $value = $this->getCallArguments($name, $arguments); + $this->data[$parameter] = $value; + $this->parameterPosition()[$parameter] = $value; + + return $this; + } + + if (strncmp($name, 'set', 3) === 0) { + $parameter = \mb_strcut($name, 3); + $with_method = "with$parameter"; + + return $this->$with_method($this->getCallArguments($name, $arguments)); + } + + throw new RuntimeException('Call to undefined method ' . __CLASS__ . '::' . $name . '()'); + } + + /** + * @param string $name + * @param array $arguments + * @param int $index + * + * @return mixed + */ + private function getCallArguments($name, array $arguments, $index = 0) + { + if (!isset($arguments[$index])) { + throw new ArgumentCountError("Missing arguments to method $name"); + } + + return $arguments[$index]; + } +} diff --git a/vendor/alibabacloud/client/src/Resolver/Roa.php b/vendor/alibabacloud/client/src/Resolver/Roa.php new file mode 100644 index 0000000..719cb0a --- /dev/null +++ b/vendor/alibabacloud/client/src/Resolver/Roa.php @@ -0,0 +1,43 @@ +resolveActionName(); + $this->appendSdkUA(); + } + + /** + * @return mixed + */ + private function ¶meterPosition() + { + return $this->pathParameters; + } +} diff --git a/vendor/alibabacloud/client/src/Resolver/Rpc.php b/vendor/alibabacloud/client/src/Resolver/Rpc.php new file mode 100644 index 0000000..0926ca4 --- /dev/null +++ b/vendor/alibabacloud/client/src/Resolver/Rpc.php @@ -0,0 +1,41 @@ +resolveActionName(); + $this->appendSdkUA(); + } + + /** + * @return mixed + */ + private function ¶meterPosition() + { + return $this->options['query']; + } +} diff --git a/vendor/alibabacloud/client/src/Resolver/VersionResolver.php b/vendor/alibabacloud/client/src/Resolver/VersionResolver.php new file mode 100644 index 0000000..cc66c64 --- /dev/null +++ b/vendor/alibabacloud/client/src/Resolver/VersionResolver.php @@ -0,0 +1,73 @@ +__call($name, $arguments); + } + + /** + * @param string $version + * @param array $arguments + * + * @return mixed + * @throws ClientException + */ + public function __call($version, $arguments) + { + $version = \ucfirst($version); + $product = $this->getProductName(); + + $position = strpos($product, 'Version'); + if ($position !== false && $position !== 0) { + $product = \str_replace('Version', '', $product); + } + + $class = "AlibabaCloud\\{$product}\\$version\\{$product}ApiResolver"; + + if (\class_exists($class)) { + return new $class(); + } + + throw new ClientException( + "$product Versions contains no {$version}", + 'SDK.VersionNotFound' + ); + } + + /** + * @return mixed + * @throws ClientException + */ + private function getProductName() + { + $array = \explode('\\', \get_class($this)); + + if (isset($array[1])) { + return $array[1]; + } + + throw new ClientException( + 'Service name not found.', + 'SDK.ServiceNotFound' + ); + } +} diff --git a/vendor/alibabacloud/client/src/Result/Result.php b/vendor/alibabacloud/client/src/Result/Result.php new file mode 100644 index 0000000..7c2910e --- /dev/null +++ b/vendor/alibabacloud/client/src/Result/Result.php @@ -0,0 +1,151 @@ +getStatusCode(), + $response->getHeaders(), + $response->getBody(), + $response->getProtocolVersion(), + $response->getReasonPhrase() + ); + + $this->request = $request; + + $this->resolveData(); + } + + private function resolveData() + { + $content = $this->getBody()->getContents(); + + switch ($this->getRequestFormat()) { + case 'JSON': + $result_data = $this->jsonToArray($content); + break; + case 'XML': + $result_data = $this->xmlToArray($content); + break; + case 'RAW': + $result_data = $this->jsonToArray($content); + break; + default: + $result_data = $this->jsonToArray($content); + } + + if (!$result_data) { + $result_data = []; + } + + $this->dot($result_data); + } + + /** + * @return string + */ + private function getRequestFormat() + { + return ($this->request instanceof Request) + ? \strtoupper($this->request->format) + : 'JSON'; + } + + /** + * @param string $response + * + * @return array + */ + private function jsonToArray($response) + { + try { + return \GuzzleHttp\json_decode($response, true); + } catch (InvalidArgumentException $exception) { + return []; + } + } + + /** + * @param string $string + * + * @return array + */ + private function xmlToArray($string) + { + try { + return json_decode(json_encode(simplexml_load_string($string)), true); + } catch (Exception $exception) { + return []; + } + } + + /** + * @return string + */ + public function __toString() + { + return (string)$this->getBody(); + } + + /** + * @return Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * @codeCoverageIgnore + * @return Response + * @deprecated + */ + public function getResponse() + { + return $this; + } + + /** + * @return bool + */ + public function isSuccess() + { + return 200 <= $this->getStatusCode() + && 300 > $this->getStatusCode(); + } +} diff --git a/vendor/alibabacloud/client/src/SDK.php b/vendor/alibabacloud/client/src/SDK.php new file mode 100644 index 0000000..8976cd0 --- /dev/null +++ b/vendor/alibabacloud/client/src/SDK.php @@ -0,0 +1,57 @@ +getMessage(), + SDK::INVALID_CREDENTIAL + ); + } + + return base64_encode($binarySignature); + } +} diff --git a/vendor/alibabacloud/client/src/Signature/Signature.php b/vendor/alibabacloud/client/src/Signature/Signature.php new file mode 100644 index 0000000..3b01e69 --- /dev/null +++ b/vendor/alibabacloud/client/src/Signature/Signature.php @@ -0,0 +1,49 @@ +sign($string, $accessKeySecret); + + return "acs $accessKeyId:$signature"; + } + + /** + * @codeCoverageIgnore + * + * @param string $accessKeySecret + * @param string $method + * @param array $parameters + * + * @return string + */ + public function rpc($accessKeySecret, $method, array $parameters) + { + $string = Sign::rpcString($method, $parameters); + + return $this->sign($string, $accessKeySecret . '&'); + } +} diff --git a/vendor/alibabacloud/client/src/Signature/SignatureInterface.php b/vendor/alibabacloud/client/src/Signature/SignatureInterface.php new file mode 100644 index 0000000..afa82c8 --- /dev/null +++ b/vendor/alibabacloud/client/src/Signature/SignatureInterface.php @@ -0,0 +1,35 @@ + $value) { + if (is_int($key)) { + $result[] = $value; + continue; + } + + if (isset($result[$key]) && is_array($result[$key])) { + $result[$key] = self::merge( + [$result[$key], $value] + ); + continue; + } + + $result[$key] = $value; + } + } + + return $result; + } +} diff --git a/vendor/alibabacloud/client/src/Support/Path.php b/vendor/alibabacloud/client/src/Support/Path.php new file mode 100644 index 0000000..e1a6464 --- /dev/null +++ b/vendor/alibabacloud/client/src/Support/Path.php @@ -0,0 +1,28 @@ + $value) { + $pattern = str_replace("[$key]", $value, $pattern); + } + + return $pattern; + } +} diff --git a/vendor/alibabacloud/client/src/Support/Sign.php b/vendor/alibabacloud/client/src/Support/Sign.php new file mode 100644 index 0000000..214319f --- /dev/null +++ b/vendor/alibabacloud/client/src/Support/Sign.php @@ -0,0 +1,140 @@ + $headerValue) { + $key = strtolower($headerKey); + if (strncmp($key, 'x-acs-', 6) === 0) { + $array[$key] = $headerValue; + } + } + ksort($array); + $string = ''; + foreach ($array as $sortMapKey => $sortMapValue) { + $string .= $sortMapKey . ':' . $sortMapValue[0] . self::$headerSeparator; + } + + return $string; + } + + /** + * @param UriInterface $uri + * + * @return string + */ + private static function resourceString(UriInterface $uri) + { + return $uri->getPath() . '?' . rawurldecode($uri->getQuery()); + } + + /** + * @param string $method + * @param array $headers + * + * @return string + */ + private static function headerString($method, array $headers) + { + $string = $method . self::$headerSeparator; + if (isset($headers['Accept'][0])) { + $string .= $headers['Accept'][0]; + } + $string .= self::$headerSeparator; + + if (isset($headers['Content-MD5'][0])) { + $string .= $headers['Content-MD5'][0]; + } + $string .= self::$headerSeparator; + + if (isset($headers['Content-Type'][0])) { + $string .= $headers['Content-Type'][0]; + } + $string .= self::$headerSeparator; + + if (isset($headers['Date'][0])) { + $string .= $headers['Date'][0]; + } + $string .= self::$headerSeparator; + + $string .= self::acsHeaderString($headers); + + return $string; + } + + /** + * @param string $string + * + * @return null|string|string[] + */ + private static function percentEncode($string) + { + $result = urlencode($string); + $result = str_replace(['+', '*'], ['%20', '%2A'], $result); + $result = preg_replace('/%7E/', '~', $result); + + return $result; + } + + /** + * @param string $method + * @param array $parameters + * + * @return string + */ + public static function rpcString($method, array $parameters) + { + ksort($parameters); + $canonicalized = ''; + foreach ($parameters as $key => $value) { + $canonicalized .= '&' . self::percentEncode($key) . '=' . self::percentEncode($value); + } + + return $method . '&%2F&' . self::percentEncode(substr($canonicalized, 1)); + } + + /** + * @param Request $request + * + * @return string + */ + public static function roaString(Request $request) + { + return self::headerString($request->getMethod(), $request->getHeaders()) . + self::resourceString($request->getUri()); + } + + /** + * @param string $salt + * + * @return string + */ + public static function uuid($salt) + { + return md5($salt . uniqid(md5(microtime(true)), true)) . microtime(); + } +} diff --git a/vendor/alibabacloud/client/src/Traits/ArrayAccessTrait.php b/vendor/alibabacloud/client/src/Traits/ArrayAccessTrait.php new file mode 100644 index 0000000..d6f6908 --- /dev/null +++ b/vendor/alibabacloud/client/src/Traits/ArrayAccessTrait.php @@ -0,0 +1,57 @@ +data[$offset])) { + return $this->data[$offset]; + } + + $value = null; + + return $value; + } + + /** + * @param string $offset + * @param string|mixed $value + */ + public function offsetSet($offset, $value) + { + $this->data[$offset] = $value; + } + + /** + * @param string $offset + * + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->data[$offset]); + } + + /** + * @param string $offset + */ + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } +} diff --git a/vendor/alibabacloud/client/src/Traits/ClientTrait.php b/vendor/alibabacloud/client/src/Traits/ClientTrait.php new file mode 100644 index 0000000..2336d17 --- /dev/null +++ b/vendor/alibabacloud/client/src/Traits/ClientTrait.php @@ -0,0 +1,273 @@ +load(); + } + $list = []; + foreach (\func_get_args() as $filename) { + $list[$filename] = (new IniCredential($filename))->load(); + } + + return $list; + } + + /** + * Custom Client. + * + * @param CredentialsInterface $credentials + * @param SignatureInterface $signature + * + * @return Client + */ + public static function client(CredentialsInterface $credentials, SignatureInterface $signature) + { + return new Client($credentials, $signature); + } + + /** + * Use the AccessKey to complete the authentication. + * + * @param string $accessKeyId + * @param string $accessKeySecret + * + * @return AccessKeyClient + * @throws ClientException + */ + public static function accessKeyClient($accessKeyId, $accessKeySecret) + { + if (strpos($accessKeyId, ' ') !== false) { + throw new ClientException( + 'AccessKey ID format is invalid', + SDK::INVALID_ARGUMENT + ); + } + + if (strpos($accessKeySecret, ' ') !== false) { + throw new ClientException( + 'AccessKey Secret format is invalid', + SDK::INVALID_ARGUMENT + ); + } + + return new AccessKeyClient($accessKeyId, $accessKeySecret); + } + + /** + * Use the AssumeRole of the RAM account to complete the authentication. + * + * @param string $accessKeyId + * @param string $accessKeySecret + * @param string $roleArn + * @param string $roleSessionName + * @param string|array $policy + * + * @return RamRoleArnClient + * @throws ClientException + */ + public static function ramRoleArnClient($accessKeyId, $accessKeySecret, $roleArn, $roleSessionName, $policy = '') + { + return new RamRoleArnClient($accessKeyId, $accessKeySecret, $roleArn, $roleSessionName, $policy); + } + + /** + * Use the RAM role of an ECS instance to complete the authentication. + * + * @param string $roleName + * + * @return EcsRamRoleClient + * @throws ClientException + */ + public static function ecsRamRoleClient($roleName) + { + return new EcsRamRoleClient($roleName); + } + + /** + * Use the Bearer Token to complete the authentication. + * + * @param string $bearerToken + * + * @return BearerTokenClient + * @throws ClientException + */ + public static function bearerTokenClient($bearerToken) + { + return new BearerTokenClient($bearerToken); + } + + /** + * Use the STS Token to complete the authentication. + * + * @param string $accessKeyId Access key ID + * @param string $accessKeySecret Access Key Secret + * @param string $securityToken Security Token + * + * @return StsClient + * @throws ClientException + */ + public static function stsClient($accessKeyId, $accessKeySecret, $securityToken = '') + { + return new StsClient($accessKeyId, $accessKeySecret, $securityToken); + } + + /** + * Use the RSA key pair to complete the authentication (supported only on Japanese site) + * + * @param string $publicKeyId + * @param string $privateKeyFile + * + * @return RsaKeyPairClient + * @throws ClientException + */ + public static function rsaKeyPairClient($publicKeyId, $privateKeyFile) + { + return new RsaKeyPairClient($publicKeyId, $privateKeyFile); + } +} diff --git a/vendor/alibabacloud/client/src/Traits/DefaultRegionTrait.php b/vendor/alibabacloud/client/src/Traits/DefaultRegionTrait.php new file mode 100644 index 0000000..5d5c75a --- /dev/null +++ b/vendor/alibabacloud/client/src/Traits/DefaultRegionTrait.php @@ -0,0 +1,66 @@ +realRegionId(); + $network = $request->network ?: 'public'; + $suffix = $request->endpointSuffix; + if ($network === 'public') { + $network = ''; + } + + if ($request->endpointRegional === 'regional') { + return "{$request->product}{$suffix}{$network}.{$regionId}.aliyuncs.com"; + } + + if ($request->endpointRegional === 'central') { + return "{$request->product}{$suffix}{$network}.aliyuncs.com"; + } + + throw new InvalidArgumentException('endpointRegional is invalid.'); + } +} diff --git a/vendor/alibabacloud/client/src/Traits/HasDataTrait.php b/vendor/alibabacloud/client/src/Traits/HasDataTrait.php new file mode 100644 index 0000000..81a03ea --- /dev/null +++ b/vendor/alibabacloud/client/src/Traits/HasDataTrait.php @@ -0,0 +1,317 @@ +dot->all()); + } + + /** + * Delete the contents of a given key or keys + * + * @param array|int|string|null $keys + */ + public function clear($keys = null) + { + $this->dot->clear($keys); + } + + /** + * Flatten an array with the given character as a key delimiter + * + * @param string $delimiter + * @param array|null $items + * @param string $prepend + * + * @return array + */ + public function flatten($delimiter = '.', $items = null, $prepend = '') + { + return $this->dot->flatten($delimiter, $items, $prepend); + } + + /** + * Return the value of a given key + * + * @param int|string|null $key + * @param mixed $default + * + * @return mixed + */ + public function get($key = null, $default = null) + { + return $this->dot->get($key, $default); + } + + /** + * Set a given key / value pair or pairs + * + * @param array|int|string $keys + * @param mixed $value + */ + public function set($keys, $value = null) + { + $this->dot->set($keys, $value); + } + + /** + * Check if a given key or keys are empty + * + * @param array|int|string|null $keys + * + * @return bool + */ + public function isEmpty($keys = null) + { + return $this->dot->isEmpty($keys); + } + + /** + * Replace all items with a given array as a reference + * + * @param array $items + */ + public function setReference(array &$items) + { + $this->dot->setReference($items); + } + + /** + * Return the value of a given key or all the values as JSON + * + * @param mixed $key + * @param int $options + * + * @return string + */ + public function toJson($key = null, $options = 0) + { + return $this->dot->toJson($key, $options); + } + + /** + * @return array + */ + public function toArray() + { + return $this->dot->all(); + } + + /** + * Check if a given key exists + * + * @param int|string $key + * + * @return bool + */ + public function offsetExists($key) + { + return $this->dot->has($key); + } + + /** + * Return the value of a given key + * + * @param int|string $key + * + * @return mixed + */ + public function offsetGet($key) + { + return $this->dot->offsetGet($key); + } + + /** + * Set a given value to the given key + * + * @param int|string|null $key + * @param mixed $value + */ + public function offsetSet($key, $value) + { + $this->dot->offsetSet($key, $value); + } + + /** + * Delete the given key + * + * @param int|string $key + */ + public function offsetUnset($key) + { + $this->delete($key); + } + + /** + * Delete the given key or keys + * + * @param array|int|string $keys + */ + public function delete($keys) + { + $this->dot->delete($keys); + } + + /* + * -------------------------------------------------------------- + * ArrayAccess interface + * -------------------------------------------------------------- + */ + + /** + * Return the number of items in a given key + * + * @param int|string|null $key + * + * @return int + */ + public function count($key = null) + { + return $this->dot->count($key); + } + + /** + * Get an iterator for the stored items + * + * @return ArrayIterator + */ + public function getIterator() + { + return $this->dot->getIterator(); + } + + /** + * Return items for JSON serialization + * + * @return array + */ + public function jsonSerialize() + { + return $this->dot->jsonSerialize(); + } + + /** + * @param string $name + * + * @return mixed|null + */ + public function __get($name) + { + if (!isset($this->all()[$name])) { + return null; + } + + return \json_decode(\json_encode($this->all()))->$name; + } + + /* + * -------------------------------------------------------------- + * Countable interface + * -------------------------------------------------------------- + */ + + /** + * Return all the stored items + * + * @return array + */ + public function all() + { + return $this->dot->all(); + } + + /** + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->add($name, $value); + } + + /** + * Set a given key / value pair or pairs + * if the key doesn't exist already + * + * @param array|int|string $keys + * @param mixed $value + */ + public function add($keys, $value = null) + { + $this->dot->add($keys, $value); + } + + + /* + * -------------------------------------------------------------- + * ObjectAccess + * -------------------------------------------------------------- + */ + + /** + * @param string $name + * + * @return bool + */ + public function __isset($name) + { + return $this->has($name); + } + + /** + * Check if a given key or keys exists + * + * @param array|int|string $keys + * + * @return bool + */ + public function has($keys) + { + return $this->dot->has($keys); + } + + /** + * @param $name + * + * @return void + */ + public function __unset($name) + { + $this->delete($name); + } + + /** + * @param array $data + */ + protected function dot(array $data = []) + { + $this->dot = new Dot($data); + } +} diff --git a/vendor/alibabacloud/client/src/Traits/HistoryTrait.php b/vendor/alibabacloud/client/src/Traits/HistoryTrait.php new file mode 100644 index 0000000..f70bb1d --- /dev/null +++ b/vendor/alibabacloud/client/src/Traits/HistoryTrait.php @@ -0,0 +1,68 @@ +options['timeout'] = ClientFilter::timeout($seconds); + + return $this; + } + + /** + * @param int $milliseconds + * + * @return $this + * @throws ClientException + */ + public function timeoutMilliseconds($milliseconds) + { + ClientFilter::milliseconds($milliseconds); + $seconds = $milliseconds / 1000; + + return $this->timeout($seconds); + } + + /** + * @param int|float $seconds + * + * @return $this + * @throws ClientException + */ + public function connectTimeout($seconds) + { + $this->options['connect_timeout'] = ClientFilter::connectTimeout($seconds); + + return $this; + } + + /** + * @param int $milliseconds + * + * @return $this + * @throws ClientException + */ + public function connectTimeoutMilliseconds($milliseconds) + { + ClientFilter::milliseconds($milliseconds); + $seconds = $milliseconds / 1000; + + return $this->connectTimeout($seconds); + } + + /** + * @param bool $debug + * + * @return $this + */ + public function debug($debug) + { + $this->options['debug'] = $debug; + + return $this; + } + + /** + * @codeCoverageIgnore + * + * @param array $cert + * + * @return $this + */ + public function cert($cert) + { + $this->options['cert'] = $cert; + + return $this; + } + + /** + * @codeCoverageIgnore + * + * @param array|string $proxy + * + * @return $this + */ + public function proxy($proxy) + { + $this->options['proxy'] = $proxy; + + return $this; + } + + /** + * @param mixed $verify + * + * @return $this + */ + public function verify($verify) + { + $this->options['verify'] = $verify; + + return $this; + } + + /** + * @param array $options + * + * @return $this + */ + public function options(array $options) + { + if ($options !== []) { + $this->options = Arrays::merge([$this->options, $options]); + } + + return $this; + } +} diff --git a/vendor/alibabacloud/client/src/Traits/LogTrait.php b/vendor/alibabacloud/client/src/Traits/LogTrait.php new file mode 100644 index 0000000..c2b778f --- /dev/null +++ b/vendor/alibabacloud/client/src/Traits/LogTrait.php @@ -0,0 +1,64 @@ +data[$name])) { + return null; + } + + return \json_decode(\json_encode($this->data))->$name; + } + + /** + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + /** + * @param string $name + * + * @return bool + */ + public function __isset($name) + { + return isset($this->data[$name]); + } + + /** + * @param $name + * + * @return void + */ + public function __unset($name) + { + unset($this->data[$name]); + } +} diff --git a/vendor/alibabacloud/client/src/Traits/RegionTrait.php b/vendor/alibabacloud/client/src/Traits/RegionTrait.php new file mode 100644 index 0000000..da6bee4 --- /dev/null +++ b/vendor/alibabacloud/client/src/Traits/RegionTrait.php @@ -0,0 +1,33 @@ +regionId = ClientFilter::regionId($regionId); + + return $this; + } +} diff --git a/vendor/alibabacloud/client/src/Traits/RequestTrait.php b/vendor/alibabacloud/client/src/Traits/RequestTrait.php new file mode 100644 index 0000000..afcb9a4 --- /dev/null +++ b/vendor/alibabacloud/client/src/Traits/RequestTrait.php @@ -0,0 +1,90 @@ + /dev/null; cd '../mtdowling/jmespath.php/bin' && pwd) + +if [ -d /proc/cygdrive ]; then + case $(which php) in + $(readlink -n /proc/cygdrive)/*) + # We are in Cygwin using Windows php, so the path must be translated + dir=$(cygpath -m "$dir"); + ;; + esac +fi + +"${dir}/jp.php" "$@" diff --git a/vendor/bin/validate-json b/vendor/bin/validate-json new file mode 100644 index 0000000..d511ff7 --- /dev/null +++ b/vendor/bin/validate-json @@ -0,0 +1,14 @@ +#!/usr/bin/env sh + +dir=$(cd "${0%[/\\]*}" > /dev/null; cd '../justinrainbow/json-schema/bin' && pwd) + +if [ -d /proc/cygdrive ]; then + case $(which php) in + $(readlink -n /proc/cygdrive)/*) + # We are in Cygwin using Windows php, so the path must be translated + dir=$(cygpath -m "$dir"); + ;; + esac +fi + +"${dir}/validate-json" "$@" diff --git a/vendor/clagiordano/weblibs-configmanager/.gitignore b/vendor/clagiordano/weblibs-configmanager/.gitignore new file mode 100644 index 0000000..e01942f --- /dev/null +++ b/vendor/clagiordano/weblibs-configmanager/.gitignore @@ -0,0 +1,2 @@ +composer.phar +vendor diff --git a/vendor/clagiordano/weblibs-configmanager/.travis.yml b/vendor/clagiordano/weblibs-configmanager/.travis.yml new file mode 100644 index 0000000..b13b30d --- /dev/null +++ b/vendor/clagiordano/weblibs-configmanager/.travis.yml @@ -0,0 +1,17 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - 7.3 + - hhvm + +before_script: + - travis_retry composer self-update + - travis_retry composer update --prefer-source --no-interaction --dev + +script: phpunit diff --git a/vendor/clagiordano/weblibs-configmanager/README.md b/vendor/clagiordano/weblibs-configmanager/README.md new file mode 100644 index 0000000..f72bf5c --- /dev/null +++ b/vendor/clagiordano/weblibs-configmanager/README.md @@ -0,0 +1,130 @@ +![BuildStatus](https://travis-ci.org/clagiordano/weblibs-configmanager.svg?branch=master) ![License](https://img.shields.io/github/license/clagiordano/weblibs-configmanager.svg) + +# weblibs-configmanager +weblibs-configmanager is a tool library for easily read and access to php config array file and direct read/write configuration file / object. + +[![SensioLabsInsight](https://insight.sensiolabs.com/projects/54c4e80c-ff15-4235-8bec-a4c71bbe3ba5/big.png)](https://insight.sensiolabs.com/projects/54c4e80c-ff15-4235-8bec-a4c71bbe3ba5) + +## Why use weblibs-configmanager ? +The purpose of this project is to propose a simple and lightweight library to manage php hierarchical configuration files. + +## Installation +The recommended way to install weblibs-configmanager is through [Composer](https://getcomposer.org). +```bash +composer require clagiordano/weblibs-configmanager +``` + +## Usage examples + +### Write a sample config file like this +``` + 'app_name', + 'db' => + array ( + 'host' => 'localhost', + 'user' => 'sample_user', + 'pass' => 'sample_pass', + 'port' => 3306, + ), + 'other' => + array ( + 'multi' => + array ( + 'deep' => + array ( + 'nested' => 'config_value', + ), + ), + ), +); + +``` + +### Instance ConfigManager object + +```php +use clagiordano\weblibs\configmanager\ConfigManager; + +/** + * Instance object to read argument file + */ +$config = new ConfigManager("configfile.php"); +``` + +### Check if a value exists into config file + +```php +/** + * Check if a value exists into config file + */ +$value = $config->existValue('app'); +``` + +### Read a simple element from config file + +```php +/** + * Read a simple element from config file + */ +$value = $config->getValue('app'); +``` + +### Access to a nested element from config + +```php +/** + * Access to a nested element from config + */ +$nestedValue = $config->getValue('other.multi.deep.nested'); +``` + +### Change config value at runtime + +```php +/** + * Change config value at runtime + */ +$this->config->setValue('other.multi.deep.nested', "SUPERNESTED"); +``` + +### Save config file with original name (OVERWRITE) + +```php +/** + * Save config file with original name (OVERWRITE) + */ +$this->config->saveConfigFile(); +``` + +### Or save config file with a different name + +```php +/** + * Save config file with original name (OVERWRITE) + */ +$this->config->saveConfigFile('/new/file/name/or/path/test.php'); +``` + +### Optionally you can also reload config file from disk after save + +```php +/** + * Optionally you can also reload config file from disk after save + */ +$this->config->saveConfigFile('/new/file/name/or/path/test.php', true); +``` + +### Load another configuration file without reinstance ConfigManager + +```php +/** + * Load another configuration file without reinstance ConfigManager + */ +$this->config->loadConfig('another_config_file.php'); +``` + +## Legal +*Copyright (C) Claudio Giordano * diff --git a/vendor/clagiordano/weblibs-configmanager/composer.json b/vendor/clagiordano/weblibs-configmanager/composer.json new file mode 100644 index 0000000..f330d2b --- /dev/null +++ b/vendor/clagiordano/weblibs-configmanager/composer.json @@ -0,0 +1,36 @@ +{ + "name": "clagiordano/weblibs-configmanager", + "description": "weblibs-configmanager is a tool library for easily read and access to php config array file and direct read/write configuration file / object", + "type": "library", + "license": "LGPL-3.0-or-later", + "keywords": ["clagiordano", "weblibs", "configuration", "manager", "tool"], + "authors": [ + { + "name": "Claudio Giordano", + "email": "claudio.giordano@autistici.org", + "role": "Developer" + } + ], + "autoload": { + "psr-4": { + "clagiordano\\weblibs\\configmanager\\": "src/" + } + }, + "require": { + "php": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "^4.8", + "clagiordano/phpunit-result-printer": "^1" + }, + "autoload-dev": { + "psr-4": { + "clagiordano\\weblibs\\configmanager\\tests\\": "tests/", + "clagiordano\\weblibs\\configmanager\\testdata\\": "testdata/" + } + }, + "scripts": { + "test": "./vendor/bin/phpunit --no-coverage", + "coverage": "./vendor/bin/phpunit" + } +} diff --git a/vendor/clagiordano/weblibs-configmanager/composer.lock b/vendor/clagiordano/weblibs-configmanager/composer.lock new file mode 100644 index 0000000..f65537e --- /dev/null +++ b/vendor/clagiordano/weblibs-configmanager/composer.lock @@ -0,0 +1,1235 @@ +{ + "_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": "4891effe3d8ee390aa06d5a8dd37fb9c", + "packages": [], + "packages-dev": [ + { + "name": "clagiordano/phpunit-result-printer", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/clagiordano/phpunit-result-printer.git", + "reference": "b4351f747af7964bcdb1cc0d1aa9fe007022b3ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clagiordano/phpunit-result-printer/zipball/b4351f747af7964bcdb1cc0d1aa9fe007022b3ac", + "reference": "b4351f747af7964bcdb1cc0d1aa9fe007022b3ac", + "shasum": "" + }, + "require": { + "phpunit/phpunit": "4.8.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "clagiordano\\PhpunitResultPrinter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Phpunit custom result printer class", + "time": "2019-07-16T10:33:26+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": "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.8.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.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-06-13T12:50:23+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06T15:47:00+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+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": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-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": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.36", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.2.2", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.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": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-06-21T08:07:12+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2015-10-02T06:51:40+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-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": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-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" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-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": "2016-08-18T05:49:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-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" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17T09:04:28+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.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": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "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": "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": "2016-10-03T07:41:43+00:00" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21T13:59:46+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/yaml", + "version": "v3.4.31", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "3dc414b7db30695bae671a1d86013d03f4ae9834" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/3dc414b7db30695bae671a1d86013d03f4ae9834", + "reference": "3dc414b7db30695bae671a1d86013d03f4ae9834", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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 Yaml Component", + "homepage": "https://symfony.com", + "time": "2019-08-20T13:31:17+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": ">=5.4" + }, + "platform-dev": [] +} diff --git a/vendor/clagiordano/weblibs-configmanager/phpunit.xml b/vendor/clagiordano/weblibs-configmanager/phpunit.xml new file mode 100644 index 0000000..25777b9 --- /dev/null +++ b/vendor/clagiordano/weblibs-configmanager/phpunit.xml @@ -0,0 +1,19 @@ + + + + + ./tests/ + + + diff --git a/vendor/clagiordano/weblibs-configmanager/src/ConfigManager.php b/vendor/clagiordano/weblibs-configmanager/src/ConfigManager.php new file mode 100644 index 0000000..58a44af --- /dev/null +++ b/vendor/clagiordano/weblibs-configmanager/src/ConfigManager.php @@ -0,0 +1,148 @@ +loadConfig($configFilePath); + } + + /** + * Load config data from file and store it into internal property + * + * @param null|string $configFilePath + * + * @return ConfigManager + */ + public function loadConfig($configFilePath = null) + { + if (!is_null($configFilePath)) { + $this->configFilePath = $configFilePath; + + if (file_exists($configFilePath)) { + $this->configData = require $configFilePath; + } + } + + return $this; + } + + /** + * Prepare and write config file on disk + * + * @param null|string $configFilePath + * @param bool $autoReloadConfig + * + * @return ConfigManager + * @throws \RuntimeException + */ + public function saveConfigFile($configFilePath = null, $autoReloadConfig = false) + { + if (is_null($configFilePath)) { + $configFilePath = $this->configFilePath; + } + + $configFileContent = "configData, true); + $configFileContent .= ";\n\n"; + + try { + file_put_contents($configFilePath, $configFileContent); + } catch (\Exception $exc) { + throw new \RuntimeException( + __METHOD__ . ": Failed to write config file to path '{$configFilePath}'" + ); + } + + if ($autoReloadConfig) { + $this->loadConfig($configFilePath); + } + + return $this; + } + + /** + * Get value pointer from config for get/set value + * + * @param string $configPath + * + * @return mixed + */ + private function & getValuePointer($configPath) + { + $configData =& $this->configData; + $parts = explode('.', $configPath); + $length = count($parts); + + for ($i = 0; $i < $length; $i++) { + if (!isset($configData[ $parts[ $i ] ])) { + $configData[ $parts[ $i ] ] = ($i === $length) ? [] : null; + } + $configData = &$configData[ $parts[ $i ] ]; + } + + return $configData; + } + + /** + * Get value from config data throught keyValue path + * + * @param string $configPath + * @param mixed $defaultValue + * + * @return mixed + */ + public function getValue($configPath, $defaultValue = null) + { + $stored = $this->getValuePointer($configPath); + + return is_null($stored) ? $defaultValue : $stored; + } + + /** + * Check if exist required config for keyValue + * + * @param string $keyValue + * + * @return mixed + */ + public function existValue($keyValue) + { + return !is_null($this->getValue($keyValue)); + } + + /** + * Set value in config path + * + * @param string $configPath + * @param mixed $newValue + * + * @return ConfigManager + */ + public function setValue($configPath, $newValue) + { + $configData = &$this->getValuePointer($configPath); + $configData = $newValue; + + return $this; + } +} diff --git a/vendor/clagiordano/weblibs-configmanager/tests/ConfigManagerTest.php b/vendor/clagiordano/weblibs-configmanager/tests/ConfigManagerTest.php new file mode 100644 index 0000000..1d12963 --- /dev/null +++ b/vendor/clagiordano/weblibs-configmanager/tests/ConfigManagerTest.php @@ -0,0 +1,113 @@ +config = new ConfigManager("TestConfigData.php"); + $this->assertInstanceOf('clagiordano\weblibs\configmanager\ConfigManager', $this->config); + + $this->assertFileExists($this->configFile); + $this->config->loadConfig($this->configFile); + } + + public function testBasicUsage() + { + $this->assertNotNull( + $this->config->getValue('app') + ); + } + + public function testFastUsage() + { + $this->assertNotNull( + $this->config->getValue('app') + ); + } + + public function testFastInvalidKey() + { + $this->assertNull( + $this->config->getValue('invalidKey') + ); + } + + public function testFastInvalidKeyWithDefault() + { + $this->assertEquals( + $this->config->getValue('invalidKey', 'defaultValue'), + 'defaultValue' + ); + } + + public function testFastNestedConfig() + { + $this->assertNotNull( + $this->config->getValue('other.multi.deep.nested') + ); + } + + public function testCheckExistConfig() + { + $this->assertTrue( + $this->config->existValue('other.multi.deep.nested') + ); + } + + public function testCheckNotExistConfig() + { + $this->assertFalse( + $this->config->existValue('invalid.config.path') + ); + } + + public function testSetValue() + { + $this->config->setValue('other.multi.deep.nested', __FUNCTION__); + + $this->assertEquals( + $this->config->getValue('other.multi.deep.nested'), + __FUNCTION__ + ); + } + + public function testFailedSaveConfig() + { + $this->setExpectedException('Exception'); + $this->config->saveConfigFile('/invalid/path'); + } + + public function testSuccessSaveConfigOnTempAndReload() + { + $this->config->setValue('other.multi.deep.nested', "SUPERNESTED"); + $this->config->saveConfigFile("/tmp/testconfig.php", true); + + $this->assertEquals( + $this->config->getValue('other.multi.deep.nested'), + "SUPERNESTED" + ); + } + + public function testOverwriteSameConfigFile() + { + $this->config->saveConfigFile(); + } + + public function testFailWriteConfig() + { + $this->setExpectedException('\RuntimeException'); + $this->config->saveConfigFile('/invalid/path/test.php'); + } +} \ No newline at end of file diff --git a/vendor/clagiordano/weblibs-configmanager/testsdata/sample_config_data.php b/vendor/clagiordano/weblibs-configmanager/testsdata/sample_config_data.php new file mode 100644 index 0000000..4a99ef0 --- /dev/null +++ b/vendor/clagiordano/weblibs-configmanager/testsdata/sample_config_data.php @@ -0,0 +1,23 @@ + 'app_name', + 'db' => + array ( + 'host' => 'localhost', + 'user' => 'sample_user', + 'pass' => 'sample_pass', + 'port' => 3306, + ), + 'other' => + array ( + 'multi' => + array ( + 'deep' => + array ( + 'nested' => 'config_value', + ), + ), + ), +); + diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..fce8549 --- /dev/null +++ b/vendor/composer/ClassLoader.php @@ -0,0 +1,445 @@ + + * 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(); + private $apcuPrefix; + + 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; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * 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) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $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 (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + 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])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + 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..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) 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..f2abadd --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,67 @@ + $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\DeprecatedException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\DirectoryNotFoundException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\FileNotFoundException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\IOException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\InvalidArgumentException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\InvalidStateException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Iterators\\CachingIterator' => $vendorDir . '/nette/utils/src/Iterators/CachingIterator.php', + 'Nette\\Iterators\\Mapper' => $vendorDir . '/nette/utils/src/Iterators/Mapper.php', + 'Nette\\Localization\\ITranslator' => $vendorDir . '/nette/utils/src/Utils/ITranslator.php', + 'Nette\\MemberAccessException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\NotImplementedException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\NotSupportedException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\OutOfRangeException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\PhpGenerator\\ClassType' => $vendorDir . '/nette/php-generator/src/PhpGenerator/ClassType.php', + 'Nette\\PhpGenerator\\Closure' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Closure.php', + 'Nette\\PhpGenerator\\Constant' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Constant.php', + 'Nette\\PhpGenerator\\Dumper' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Dumper.php', + 'Nette\\PhpGenerator\\Factory' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Factory.php', + 'Nette\\PhpGenerator\\GlobalFunction' => $vendorDir . '/nette/php-generator/src/PhpGenerator/GlobalFunction.php', + 'Nette\\PhpGenerator\\Helpers' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Helpers.php', + 'Nette\\PhpGenerator\\Method' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Method.php', + 'Nette\\PhpGenerator\\Parameter' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Parameter.php', + 'Nette\\PhpGenerator\\PhpFile' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PhpFile.php', + 'Nette\\PhpGenerator\\PhpLiteral' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PhpLiteral.php', + 'Nette\\PhpGenerator\\PhpNamespace' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PhpNamespace.php', + 'Nette\\PhpGenerator\\Printer' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Printer.php', + 'Nette\\PhpGenerator\\Property' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Property.php', + 'Nette\\PhpGenerator\\PsrPrinter' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PsrPrinter.php', + 'Nette\\PhpGenerator\\Traits\\CommentAware' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/CommentAware.php', + 'Nette\\PhpGenerator\\Traits\\FunctionLike' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php', + 'Nette\\PhpGenerator\\Traits\\NameAware' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/NameAware.php', + 'Nette\\PhpGenerator\\Traits\\VisibilityAware' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php', + 'Nette\\SmartObject' => $vendorDir . '/nette/utils/src/Utils/SmartObject.php', + 'Nette\\StaticClass' => $vendorDir . '/nette/utils/src/Utils/StaticClass.php', + 'Nette\\UnexpectedValueException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ArrayHash' => $vendorDir . '/nette/utils/src/Utils/ArrayHash.php', + 'Nette\\Utils\\ArrayList' => $vendorDir . '/nette/utils/src/Utils/ArrayList.php', + 'Nette\\Utils\\Arrays' => $vendorDir . '/nette/utils/src/Utils/Arrays.php', + 'Nette\\Utils\\AssertionException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Callback' => $vendorDir . '/nette/utils/src/Utils/Callback.php', + 'Nette\\Utils\\DateTime' => $vendorDir . '/nette/utils/src/Utils/DateTime.php', + 'Nette\\Utils\\FileSystem' => $vendorDir . '/nette/utils/src/Utils/FileSystem.php', + 'Nette\\Utils\\Html' => $vendorDir . '/nette/utils/src/Utils/Html.php', + 'Nette\\Utils\\IHtmlString' => $vendorDir . '/nette/utils/src/Utils/IHtmlString.php', + 'Nette\\Utils\\Image' => $vendorDir . '/nette/utils/src/Utils/Image.php', + 'Nette\\Utils\\ImageException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Json' => $vendorDir . '/nette/utils/src/Utils/Json.php', + 'Nette\\Utils\\JsonException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ObjectHelpers' => $vendorDir . '/nette/utils/src/Utils/ObjectHelpers.php', + 'Nette\\Utils\\ObjectMixin' => $vendorDir . '/nette/utils/src/Utils/ObjectMixin.php', + 'Nette\\Utils\\Paginator' => $vendorDir . '/nette/utils/src/Utils/Paginator.php', + 'Nette\\Utils\\Random' => $vendorDir . '/nette/utils/src/Utils/Random.php', + 'Nette\\Utils\\Reflection' => $vendorDir . '/nette/utils/src/Utils/Reflection.php', + 'Nette\\Utils\\RegexpException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Strings' => $vendorDir . '/nette/utils/src/Utils/Strings.php', + 'Nette\\Utils\\UnknownImageFileException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Validators' => $vendorDir . '/nette/utils/src/Utils/Validators.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..0c5f0f5 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,23 @@ + $vendorDir . '/topthink/think-helper/src/helper.php', + '538ca81a9a966a6716601ecf48f4eaef' => $vendorDir . '/opis/closure/functions.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', + 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', + 'd767e4fc2dc52fe66584ab8c6684783e' => $vendorDir . '/adbario/php-dot-notation/src/helpers.php', + '65fec9ebcfbb3cbb4fd0d519687aea01' => $vendorDir . '/danielstjules/stringy/src/Create.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', + 'b067bc7112e384b61c701452d53a14a8' => $vendorDir . '/mtdowling/jmespath.php/src/JmesPath.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '66453932bc1be9fb2f910a27947d11b6' => $vendorDir . '/alibabacloud/client/src/Functions.php', + 'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php', + 'af46dcea2921209ac30627b964175f13' => $vendorDir . '/topthink/think-swoole/src/helpers.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..7cb803d --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,10 @@ + array($baseDir . '/extend'), +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..47ae48f --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,38 @@ + array($vendorDir . '/topthink/think-view/src'), + 'think\\swoole\\' => array($vendorDir . '/topthink/think-swoole/src'), + 'think\\log\\driver\\' => array($vendorDir . '/topthink/think-socketlog/src'), + 'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'), + 'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/think-template/src'), + 'longbingcore\\' => array($baseDir . '/longbingcore'), + 'clagiordano\\weblibs\\configmanager\\' => array($vendorDir . '/clagiordano/weblibs-configmanager/src'), + 'app\\' => array($baseDir . '/app'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Stringy\\' => array($vendorDir . '/danielstjules/stringy/src'), + 'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'), + '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'), + 'Predis\\' => array($vendorDir . '/predis/predis/src'), + 'Opis\\Closure\\' => array($vendorDir . '/opis/closure/src'), + 'League\\Flysystem\\Cached\\' => array($vendorDir . '/league/flysystem-cached-adapter/src'), + 'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'), + 'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'), + 'JmesPath\\' => array($vendorDir . '/mtdowling/jmespath.php/src'), + 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), + 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), + 'AlibabaCloud\\Client\\' => array($vendorDir . '/alibabacloud/client/src'), + 'Adbar\\' => array($vendorDir . '/adbario/php-dot-notation/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..8331b8d --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit49deecac241f46500c1443ce972b1814::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\ComposerStaticInit49deecac241f46500c1443ce972b1814::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire49deecac241f46500c1443ce972b1814($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire49deecac241f46500c1443ce972b1814($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..388b691 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,291 @@ + __DIR__ . '/..' . '/topthink/think-helper/src/helper.php', + '538ca81a9a966a6716601ecf48f4eaef' => __DIR__ . '/..' . '/opis/closure/functions.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', + 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', + 'd767e4fc2dc52fe66584ab8c6684783e' => __DIR__ . '/..' . '/adbario/php-dot-notation/src/helpers.php', + '65fec9ebcfbb3cbb4fd0d519687aea01' => __DIR__ . '/..' . '/danielstjules/stringy/src/Create.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', + 'b067bc7112e384b61c701452d53a14a8' => __DIR__ . '/..' . '/mtdowling/jmespath.php/src/JmesPath.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '66453932bc1be9fb2f910a27947d11b6' => __DIR__ . '/..' . '/alibabacloud/client/src/Functions.php', + 'e39a8b23c42d4e1452234d762b03835a' => __DIR__ . '/..' . '/ramsey/uuid/src/functions.php', + 'af46dcea2921209ac30627b964175f13' => __DIR__ . '/..' . '/topthink/think-swoole/src/helpers.php', + ); + + public static $prefixLengthsPsr4 = array ( + 't' => + array ( + 'think\\view\\driver\\' => 18, + 'think\\swoole\\' => 13, + 'think\\log\\driver\\' => 17, + 'think\\app\\' => 10, + 'think\\' => 6, + ), + 'l' => + array ( + 'longbingcore\\' => 13, + ), + 'c' => + array ( + 'clagiordano\\weblibs\\configmanager\\' => 34, + ), + 'a' => + array ( + 'app\\' => 4, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Component\\Finder\\' => 25, + 'Stringy\\' => 8, + ), + 'R' => + array ( + 'Ramsey\\Uuid\\' => 12, + ), + 'P' => + array ( + 'Psr\\SimpleCache\\' => 16, + 'Psr\\Log\\' => 8, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Container\\' => 14, + 'Psr\\Cache\\' => 10, + 'Predis\\' => 7, + ), + 'O' => + array ( + 'Opis\\Closure\\' => 13, + ), + 'L' => + array ( + 'League\\Flysystem\\Cached\\' => 24, + 'League\\Flysystem\\' => 17, + ), + 'J' => + array ( + 'JsonSchema\\' => 11, + 'JmesPath\\' => 9, + ), + 'G' => + array ( + 'GuzzleHttp\\Psr7\\' => 16, + 'GuzzleHttp\\Promise\\' => 19, + 'GuzzleHttp\\' => 11, + ), + 'A' => + array ( + 'AlibabaCloud\\Client\\' => 20, + 'Adbar\\' => 6, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'think\\view\\driver\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-view/src', + ), + 'think\\swoole\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-swoole/src', + ), + 'think\\log\\driver\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-socketlog/src', + ), + 'think\\app\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-multi-app/src', + ), + 'think\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/framework/src/think', + 1 => __DIR__ . '/..' . '/topthink/think-helper/src', + 2 => __DIR__ . '/..' . '/topthink/think-orm/src', + 3 => __DIR__ . '/..' . '/topthink/think-template/src', + ), + 'longbingcore\\' => + array ( + 0 => __DIR__ . '/../..' . '/longbingcore', + ), + 'clagiordano\\weblibs\\configmanager\\' => + array ( + 0 => __DIR__ . '/..' . '/clagiordano/weblibs-configmanager/src', + ), + 'app\\' => + array ( + 0 => __DIR__ . '/../..' . '/app', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), + 'Stringy\\' => + array ( + 0 => __DIR__ . '/..' . '/danielstjules/stringy/src', + ), + 'Ramsey\\Uuid\\' => + array ( + 0 => __DIR__ . '/..' . '/ramsey/uuid/src', + ), + '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', + ), + 'Predis\\' => + array ( + 0 => __DIR__ . '/..' . '/predis/predis/src', + ), + 'Opis\\Closure\\' => + array ( + 0 => __DIR__ . '/..' . '/opis/closure/src', + ), + 'League\\Flysystem\\Cached\\' => + array ( + 0 => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src', + ), + 'League\\Flysystem\\' => + array ( + 0 => __DIR__ . '/..' . '/league/flysystem/src', + ), + 'JsonSchema\\' => + array ( + 0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema', + ), + 'JmesPath\\' => + array ( + 0 => __DIR__ . '/..' . '/mtdowling/jmespath.php/src', + ), + 'GuzzleHttp\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', + ), + 'GuzzleHttp\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', + ), + 'GuzzleHttp\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', + ), + 'AlibabaCloud\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/alibabacloud/client/src', + ), + 'Adbar\\' => + array ( + 0 => __DIR__ . '/..' . '/adbario/php-dot-notation/src', + ), + ); + + public static $fallbackDirsPsr0 = array ( + 0 => __DIR__ . '/../..' . '/extend', + ); + + public static $classMap = array ( + 'Nette\\ArgumentOutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\DeprecatedException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\DirectoryNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\FileNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\IOException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\InvalidArgumentException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\InvalidStateException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Iterators\\CachingIterator' => __DIR__ . '/..' . '/nette/utils/src/Iterators/CachingIterator.php', + 'Nette\\Iterators\\Mapper' => __DIR__ . '/..' . '/nette/utils/src/Iterators/Mapper.php', + 'Nette\\Localization\\ITranslator' => __DIR__ . '/..' . '/nette/utils/src/Utils/ITranslator.php', + 'Nette\\MemberAccessException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\NotImplementedException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\NotSupportedException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\OutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\PhpGenerator\\ClassType' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/ClassType.php', + 'Nette\\PhpGenerator\\Closure' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Closure.php', + 'Nette\\PhpGenerator\\Constant' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Constant.php', + 'Nette\\PhpGenerator\\Dumper' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Dumper.php', + 'Nette\\PhpGenerator\\Factory' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Factory.php', + 'Nette\\PhpGenerator\\GlobalFunction' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/GlobalFunction.php', + 'Nette\\PhpGenerator\\Helpers' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Helpers.php', + 'Nette\\PhpGenerator\\Method' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Method.php', + 'Nette\\PhpGenerator\\Parameter' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Parameter.php', + 'Nette\\PhpGenerator\\PhpFile' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PhpFile.php', + 'Nette\\PhpGenerator\\PhpLiteral' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PhpLiteral.php', + 'Nette\\PhpGenerator\\PhpNamespace' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PhpNamespace.php', + 'Nette\\PhpGenerator\\Printer' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Printer.php', + 'Nette\\PhpGenerator\\Property' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Property.php', + 'Nette\\PhpGenerator\\PsrPrinter' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PsrPrinter.php', + 'Nette\\PhpGenerator\\Traits\\CommentAware' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/CommentAware.php', + 'Nette\\PhpGenerator\\Traits\\FunctionLike' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php', + 'Nette\\PhpGenerator\\Traits\\NameAware' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/NameAware.php', + 'Nette\\PhpGenerator\\Traits\\VisibilityAware' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php', + 'Nette\\SmartObject' => __DIR__ . '/..' . '/nette/utils/src/Utils/SmartObject.php', + 'Nette\\StaticClass' => __DIR__ . '/..' . '/nette/utils/src/Utils/StaticClass.php', + 'Nette\\UnexpectedValueException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ArrayHash' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayHash.php', + 'Nette\\Utils\\ArrayList' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayList.php', + 'Nette\\Utils\\Arrays' => __DIR__ . '/..' . '/nette/utils/src/Utils/Arrays.php', + 'Nette\\Utils\\AssertionException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Callback' => __DIR__ . '/..' . '/nette/utils/src/Utils/Callback.php', + 'Nette\\Utils\\DateTime' => __DIR__ . '/..' . '/nette/utils/src/Utils/DateTime.php', + 'Nette\\Utils\\FileSystem' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileSystem.php', + 'Nette\\Utils\\Html' => __DIR__ . '/..' . '/nette/utils/src/Utils/Html.php', + 'Nette\\Utils\\IHtmlString' => __DIR__ . '/..' . '/nette/utils/src/Utils/IHtmlString.php', + 'Nette\\Utils\\Image' => __DIR__ . '/..' . '/nette/utils/src/Utils/Image.php', + 'Nette\\Utils\\ImageException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Json' => __DIR__ . '/..' . '/nette/utils/src/Utils/Json.php', + 'Nette\\Utils\\JsonException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ObjectHelpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectHelpers.php', + 'Nette\\Utils\\ObjectMixin' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectMixin.php', + 'Nette\\Utils\\Paginator' => __DIR__ . '/..' . '/nette/utils/src/Utils/Paginator.php', + 'Nette\\Utils\\Random' => __DIR__ . '/..' . '/nette/utils/src/Utils/Random.php', + 'Nette\\Utils\\Reflection' => __DIR__ . '/..' . '/nette/utils/src/Utils/Reflection.php', + 'Nette\\Utils\\RegexpException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Strings' => __DIR__ . '/..' . '/nette/utils/src/Utils/Strings.php', + 'Nette\\Utils\\UnknownImageFileException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Validators' => __DIR__ . '/..' . '/nette/utils/src/Utils/Validators.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit49deecac241f46500c1443ce972b1814::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit49deecac241f46500c1443ce972b1814::$prefixDirsPsr4; + $loader->fallbackDirsPsr0 = ComposerStaticInit49deecac241f46500c1443ce972b1814::$fallbackDirsPsr0; + $loader->classMap = ComposerStaticInit49deecac241f46500c1443ce972b1814::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..d0f0e90 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,1966 @@ +[ + { + "name": "adbario/php-dot-notation", + "version": "2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/adbario/php-dot-notation.git", + "reference": "eee4fc81296531e6aafba4c2bbccfc5adab1676e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/adbario/php-dot-notation/zipball/eee4fc81296531e6aafba4c2bbccfc5adab1676e", + "reference": "eee4fc81296531e6aafba4c2bbccfc5adab1676e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.0|^5.0|^6.0", + "squizlabs/php_codesniffer": "^3.0" + }, + "time": "2019-01-01T23:59:15+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Adbar\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Riku Särkinen", + "email": "riku@adbar.io" + } + ], + "description": "PHP dot notation access to arrays", + "homepage": "https://github.com/adbario/php-dot-notation", + "keywords": [ + "ArrayAccess", + "dotnotation" + ] + }, + { + "name": "alibabacloud/client", + "version": "1.5.21", + "version_normalized": "1.5.21.0", + "source": { + "type": "git", + "url": "https://github.com/aliyun/openapi-sdk-php-client.git", + "reference": "a11d68d90d50d2bde46c3a656eef5e0563c08b69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aliyun/openapi-sdk-php-client/zipball/a11d68d90d50d2bde46c3a656eef5e0563c08b69", + "reference": "a11d68d90d50d2bde46c3a656eef5e0563c08b69", + "shasum": "" + }, + "require": { + "adbario/php-dot-notation": "^2.2", + "clagiordano/weblibs-configmanager": "^1.0", + "danielstjules/stringy": "^3.1", + "ext-curl": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "ext-xmlwriter": "*", + "guzzlehttp/guzzle": "^6.3", + "mtdowling/jmespath.php": "^2.4", + "php": ">=5.5" + }, + "require-dev": { + "composer/composer": "^1.8", + "drupal/coder": "^8.3", + "ext-dom": "*", + "ext-pcre": "*", + "ext-sockets": "*", + "ext-spl": "*", + "league/climate": "^3.2.4", + "mikey179/vfsstream": "^1.6", + "monolog/monolog": "^1.24", + "phpunit/phpunit": "^4.8.35|^5.4.3", + "psr/cache": "^1.0", + "symfony/dotenv": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "suggest": { + "ext-sockets": "To use client-side monitoring" + }, + "time": "2020-02-26T04:39:45+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "AlibabaCloud\\Client\\": "src" + }, + "files": [ + "src/Functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alibaba Cloud SDK", + "email": "sdk-team@alibabacloud.com", + "homepage": "http://www.alibabacloud.com" + } + ], + "description": "Alibaba Cloud Client for PHP - Use Alibaba Cloud in your PHP project", + "homepage": "https://www.alibabacloud.com/", + "keywords": [ + "alibaba", + "alibabacloud", + "aliyun", + "client", + "cloud", + "library", + "sdk", + "tool" + ] + }, + { + "name": "clagiordano/weblibs-configmanager", + "version": "v1.0.7", + "version_normalized": "1.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/clagiordano/weblibs-configmanager.git", + "reference": "6ef4c27354368deb2f54b39bbe06601da8c873a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clagiordano/weblibs-configmanager/zipball/6ef4c27354368deb2f54b39bbe06601da8c873a0", + "reference": "6ef4c27354368deb2f54b39bbe06601da8c873a0", + "shasum": "" + }, + "require": { + "php": ">=5.4" + }, + "require-dev": { + "clagiordano/phpunit-result-printer": "^1", + "phpunit/phpunit": "^4.8" + }, + "time": "2019-09-25T22:10:10+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "clagiordano\\weblibs\\configmanager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Claudio Giordano", + "email": "claudio.giordano@autistici.org", + "role": "Developer" + } + ], + "description": "weblibs-configmanager is a tool library for easily read and access to php config array file and direct read/write configuration file / object", + "keywords": [ + "clagiordano", + "configuration", + "manager", + "tool", + "weblibs" + ] + }, + { + "name": "danielstjules/stringy", + "version": "3.1.0", + "version_normalized": "3.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/danielstjules/Stringy.git", + "reference": "df24ab62d2d8213bbbe88cc36fc35a4503b4bd7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/danielstjules/Stringy/zipball/df24ab62d2d8213bbbe88cc36fc35a4503b4bd7e", + "reference": "df24ab62d2d8213bbbe88cc36fc35a4503b4bd7e", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2017-06-12T01:10:27+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Stringy\\": "src/" + }, + "files": [ + "src/Create.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel St. Jules", + "email": "danielst.jules@gmail.com", + "homepage": "http://www.danielstjules.com" + } + ], + "description": "A string manipulation library with multibyte support", + "homepage": "https://github.com/danielstjules/Stringy", + "keywords": [ + "UTF", + "helpers", + "manipulation", + "methods", + "multibyte", + "string", + "utf-8", + "utility", + "utils" + ] + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.5.1", + "version_normalized": "6.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "0274c05370a7bc9bb3a33838858253418bd7d14b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0274c05370a7bc9bb3a33838858253418bd7d14b", + "reference": "0274c05370a7bc9bb3a33838858253418bd7d14b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "time": "2019-12-21T08:51:15+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ] + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "version_normalized": "1.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "time": "2016-12-20T10:07:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-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": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ] + }, + { + "name": "guzzlehttp/psr7", + "version": "1.6.1", + "version_normalized": "1.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", + "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.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, + "time": "2019-07-01T23:21:34+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "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": "justinrainbow/json-schema", + "version": "1.6.1", + "version_normalized": "1.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/cc84765fb7317f6b07bd8ac78364747f95b86341", + "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341", + "shasum": "" + }, + "require": { + "php": ">=5.3.29" + }, + "require-dev": { + "json-schema/json-schema-test-suite": "1.1.0", + "phpdocumentor/phpdocumentor": "~2", + "phpunit/phpunit": "~3.7" + }, + "time": "2016-01-25T15:43:01+00:00", + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ] + }, + { + "name": "league/flysystem", + "version": "1.0.61", + "version_normalized": "1.0.61.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "4fb13c01784a6c9f165a351e996871488ca2d8c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4fb13c01784a6c9f165a351e996871488ca2d8c9", + "reference": "4fb13c01784a6c9f165a351e996871488ca2d8c9", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7.10" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "time": "2019-12-08T21:46:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ] + }, + { + "name": "league/flysystem-cached-adapter", + "version": "1.0.9", + "version_normalized": "1.0.9.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-cached-adapter.git", + "reference": "08ef74e9be88100807a3b92cc9048a312bf01d6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-cached-adapter/zipball/08ef74e9be88100807a3b92cc9048a312bf01d6f", + "reference": "08ef74e9be88100807a3b92cc9048a312bf01d6f", + "shasum": "" + }, + "require": { + "league/flysystem": "~1.0", + "psr/cache": "^1.0.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0", + "tedivm/stash": "~0.12" + }, + "suggest": { + "ext-phpredis": "Pure C implemented extension for PHP" + }, + "time": "2018-07-09T20:51:04+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\Flysystem\\Cached\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "frankdejonge", + "email": "info@frenky.net" + } + ], + "description": "An adapter decorator to enable meta-data caching." + }, + { + "name": "mtdowling/jmespath.php", + "version": "2.5.0", + "version_normalized": "2.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "52168cb9472de06979613d365c7f1ab8798be895" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/52168cb9472de06979613d365c7f1ab8798be895", + "reference": "52168cb9472de06979613d365c7f1ab8798be895", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "symfony/polyfill-mbstring": "^1.4" + }, + "require-dev": { + "composer/xdebug-handler": "^1.2", + "phpunit/phpunit": "^4.8.36|^7.5.15" + }, + "time": "2019-12-30T18:03:34+00:00", + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ] + }, + { + "name": "nette/php-generator", + "version": "v3.3.1", + "version_normalized": "3.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/nette/php-generator.git", + "reference": "4240fd7adf499138c07b814ef9b9a6df9f6d7187" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/php-generator/zipball/4240fd7adf499138c07b814ef9b9a6df9f6d7187", + "reference": "4240fd7adf499138c07b814ef9b9a6df9f6d7187", + "shasum": "" + }, + "require": { + "nette/utils": "^2.4.2 || ~3.0.0", + "php": ">=7.1" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "time": "2019-11-22T11:12:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.3 features.", + "homepage": "https://nette.org", + "keywords": [ + "code", + "nette", + "php", + "scaffolding" + ] + }, + { + "name": "nette/utils", + "version": "v3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "c133e18c922dcf3ad07673077d92d92cef25a148" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/c133e18c922dcf3ad07673077d92d92cef25a148", + "reference": "c133e18c922dcf3ad07673077d92d92cef25a148", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "nette/tester": "~2.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize() and toAscii()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "time": "2019-10-21T20:40:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ] + }, + { + "name": "opis/closure", + "version": "3.5.1", + "version_normalized": "3.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/93ebc5712cdad8d5f489b500c59d122df2e53969", + "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "time": "2019-11-29T22:36:02+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.5.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Opis\\Closure\\": "src/" + }, + "files": [ + "functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ] + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "version_normalized": "9.99.99.0", + "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." + }, + "time": "2018-07-02T15:55:56+00:00", + "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": "predis/predis", + "version": "v1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/nrk/predis.git", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + }, + "time": "2016-06-16T16:22:20+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net" + } + ], + "description": "Flexible and feature-complete Redis client for PHP and HHVM", + "homepage": "http://github.com/nrk/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ] + }, + { + "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": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T20:24:11+00:00", + "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": "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": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-02-14T16:28:37+00:00", + "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/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": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T14:39:51+00:00", + "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": "psr/log", + "version": "1.1.2", + "version_normalized": "1.1.2.0", + "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" + }, + "time": "2019-11-01T11:05:21+00:00", + "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": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "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": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-10-23T01:57:42+00:00", + "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": "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": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "time": "2019-03-08T08:55:37+00:00", + "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": "ramsey/uuid", + "version": "3.9.2", + "version_normalized": "3.9.2.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "7779489a47d443f845271badbdcedfe4df8e06fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/7779489a47d443f845271badbdcedfe4df8e06fb", + "reference": "7779489a47d443f845271badbdcedfe4df8e06fb", + "shasum": "" + }, + "require": { + "ext-json": "*", + "paragonie/random_compat": "^1 | ^2 | 9.99.99", + "php": "^5.4 | ^7 | ^8", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^1 | ^2", + "doctrine/annotations": "^1.2", + "goaop/framework": "1.0.0-alpha.2 | ^1 | ^2.1", + "jakub-onderka/php-parallel-lint": "^1", + "mockery/mockery": "^0.9.11 | ^1", + "moontoast/math": "^1.1", + "paragonie/random-lib": "^2", + "php-mock/php-mock-phpunit": "^0.3 | ^1.1", + "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "ext-ctype": "Provides support for PHP Ctype functions", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "time": "2019-12-17T08:18:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + }, + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ] + }, + { + "name": "swoole/ide-helper", + "version": "4.4.12", + "version_normalized": "4.4.12.0", + "source": { + "type": "git", + "url": "https://github.com/swoole/ide-helper.git", + "reference": "915964abd1ff4fe78be3e341ce9008b6d6a7648a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swoole/ide-helper/zipball/915964abd1ff4fe78be3e341ce9008b6d6a7648a", + "reference": "915964abd1ff4fe78be3e341ce9008b6d6a7648a", + "shasum": "" + }, + "require-dev": { + "squizlabs/php_codesniffer": "~3.4.0", + "symfony/filesystem": "~4.3.0", + "zendframework/zend-code": "~3.3.0" + }, + "time": "2019-11-04T17:53:06+00:00", + "type": "library", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Team Swoole", + "email": "team@swoole.com" + } + ], + "description": "IDE help files for Swoole." + }, + { + "name": "symfony/finder", + "version": "v4.4.2", + "version_normalized": "4.4.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "ce8743441da64c41e2a667b8eb66070444ed911e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/ce8743441da64c41e2a667b8eb66070444ed911e", + "reference": "ce8743441da64c41e2a667b8eb66070444ed911e", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "time": "2019-11-17T21:56:56+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.13.1", + "version_normalized": "1.13.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2019-11-27T13:56:44+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.14.0", + "version_normalized": "1.14.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/34094cfa9abe1f0f14f48f490772db7a775559f2", + "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2020-01-13T11:15:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.14-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "topthink/framework", + "version": "6.0.x-dev", + "version_normalized": "6.0.9999999.9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/top-think/framework.git", + "reference": "2e57ce8ccfddadf9b31fb41ac96a8cf15ee034f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/framework/zipball/2e57ce8ccfddadf9b31fb41ac96a8cf15ee034f0", + "reference": "2e57ce8ccfddadf9b31fb41ac96a8cf15ee034f0", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "league/flysystem": "^1.0", + "league/flysystem-cached-adapter": "^1.0", + "opis/closure": "^3.1", + "php": ">=7.1.0", + "psr/container": "~1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-helper": "^3.1.1", + "topthink/think-orm": "^2.0" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^7.0" + }, + "time": "2019-12-22T12:44:23+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [], + "psr-4": { + "think\\": "src/think/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP Framework.", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "framework", + "orm", + "thinkphp" + ] + }, + { + "name": "topthink/think-helper", + "version": "v3.1.3", + "version_normalized": "3.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "4d85dfd3778623bbb1de3648f1dcd0c82f4439f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/4d85dfd3778623bbb1de3648f1dcd0c82f4439f4", + "reference": "4d85dfd3778623bbb1de3648f1dcd0c82f4439f4", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "time": "2019-09-30T02:36:48+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "src/helper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6 Helper Package" + }, + { + "name": "topthink/think-multi-app", + "version": "v1.0.11", + "version_normalized": "1.0.11.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-multi-app.git", + "reference": "215f4a6bb88e53ad41b448c61957336eb55ce6f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-multi-app/zipball/215f4a6bb88e53ad41b448c61957336eb55ce6f9", + "reference": "215f4a6bb88e53ad41b448c61957336eb55ce6f9", + "shasum": "" + }, + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0.0" + }, + "time": "2019-10-29T06:34:59+00:00", + "type": "library", + "extra": { + "think": { + "services": [ + "think\\app\\Service" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\app\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp6 multi app support" + }, + { + "name": "topthink/think-orm", + "version": "v2.0.28", + "version_normalized": "2.0.28.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-orm.git", + "reference": "ff5f112f5559497222f2716385b5a709ab82741b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-orm/zipball/ff5f112f5559497222f2716385b5a709ab82741b", + "reference": "ff5f112f5559497222f2716385b5a709ab82741b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-helper": "^3.1" + }, + "time": "2019-11-17T07:53:45+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "think orm", + "keywords": [ + "database", + "orm" + ] + }, + { + "name": "topthink/think-socketlog", + "version": "v1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-socketlog.git", + "reference": "fe7134c41026dc74176796d4a6b7f57ae3fa1a84" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-socketlog/zipball/fe7134c41026dc74176796d4a6b7f57ae3fa1a84", + "reference": "fe7134c41026dc74176796d4a6b7f57ae3fa1a84", + "shasum": "" + }, + "require": { + "topthink/framework": "^6.0.0" + }, + "time": "2019-06-10T08:15:30+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\log\\driver\\": "src" + }, + "files": [] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "SocketLog for thinkphp" + }, + { + "name": "topthink/think-swoole", + "version": "v3.0.5", + "version_normalized": "3.0.5.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-swoole.git", + "reference": "e36eff2f2bd88b5fe30631f55aa6b1c27b89aefa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-swoole/zipball/e36eff2f2bd88b5fe30631f55aa6b1c27b89aefa", + "reference": "e36eff2f2bd88b5fe30631f55aa6b1c27b89aefa", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-swoole": ">=4.0.0", + "nette/php-generator": "^3.2", + "swoole/ide-helper": "^4.3", + "symfony/finder": "^4.3.2", + "topthink/framework": "^6.0", + "topthink/think-helper": "^3.0.4" + }, + "require-dev": { + "symfony/var-dumper": "^4.3" + }, + "time": "2019-10-21T12:03:07+00:00", + "type": "library", + "extra": { + "think": { + "services": [ + "think\\swoole\\Service" + ], + "config": { + "swoole": "src/config/swoole.php" + } + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\swoole\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "Swoole extend for thinkphp" + }, + { + "name": "topthink/think-template", + "version": "v2.0.7", + "version_normalized": "2.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-template.git", + "reference": "e98bdbb4a4c94b442f17dfceba81e0134d4fbd19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-template/zipball/e98bdbb4a4c94b442f17dfceba81e0134d4fbd19", + "reference": "e98bdbb4a4c94b442f17dfceba81e0134d4fbd19", + "shasum": "" + }, + "require": { + "php": ">=7.1.0", + "psr/simple-cache": "^1.0" + }, + "time": "2019-09-20T15:31:04+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the php template engine" + }, + { + "name": "topthink/think-view", + "version": "v1.0.13", + "version_normalized": "1.0.13.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-view.git", + "reference": "90803b73f781db5d42619082c4597afc58b2d4c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-view/zipball/90803b73f781db5d42619082c4597afc58b2d4c5", + "reference": "90803b73f781db5d42619082c4597afc58b2d4c5", + "shasum": "" + }, + "require": { + "php": ">=7.1.0", + "topthink/think-template": "^2.0" + }, + "time": "2019-10-07T12:23:10+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\view\\driver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp template driver" + } +] diff --git a/vendor/danielstjules/stringy/CHANGELOG.md b/vendor/danielstjules/stringy/CHANGELOG.md new file mode 100644 index 0000000..3f13575 --- /dev/null +++ b/vendor/danielstjules/stringy/CHANGELOG.md @@ -0,0 +1,180 @@ +### 3.1.0 (2017-06-11) +* Add $language support to slugify +* Add bg specific transliteration +* ЬЪ/ьъ handling is now language-specific + +### 3.0.1 (2017-04-12) +* Don't replace @ in toAscii +* Use normal replacement for @ in slugify, e.g. user@home => user-home + +### 3.0.0 (2017-03-08) + +* Breaking change: added $language parameter to toAscii, before + $removeUnsupported +* Breaking change: dropped PHP 5.3 support +* Breaking change: any StaticStringy methods that previously returned instances + of Stringy now return strings + +### 2.4.0 (2017-03-02) + +* Add startsWithAny +* Add endsWithAny +* Add stripWhitespace +* Fix error handling for unsupported encodings +* Change private methods to protected for extending class +* Fix safeTruncate for strings without spaces +* Additional char support in toAscii, e.g. full width chars and wide + non-breaking space + +### 2.3.2 (2016-05-02) + +* Improve support without mbstring + +### 2.3.1 (2016-03-21) + +* Always use root namespace for mbstring functions + +### 2.3.0 (2016-03-19) + +* Add Persian characters in Stringy::charsArray() +* Use symfony/polyfill-mbstring to avoid dependency on ext-mbstring + +### 2.2.0 (2015-12-20) + +* isJSON now returns false for empty strings +* Update for German umlaut transformation +* Use reflection to generate method list for StaticStringy +* Added isBase64 method +* Improved toAscii char coverage + +### 2.1.0 (2015-09-02) + +* Added simplified StaticStringy class +* str in Stringy::create and constructor is now optional + +### 2.0.0 (2015-07-29) + + * Removed StaticStringy class + * Added append, prepend, toBoolean, repeat, between, slice, split, and lines + * camelize/upperCamelize now strip leading dashes and underscores + * titleize converts to lowercase, thus no longer preserving acronyms + +### 1.10.0 (2015-07-22) + + * Added trimLeft, trimRight + * Added support for unicode whitespace to trim + * Added delimit + * Added indexOf and indexOfLast + * Added htmlEncode and htmlDecode + * Added "Ç" in toAscii() + +### 1.9.0 (2015-02-09) + + * Added hasUpperCase and hasLowerCase + * Added $removeUnsupported parameter to toAscii() + * Improved toAscii support with additional Unicode spaces, Vietnamese chars, + and numerous other characters + * Separated the charsArray from toAscii as a protected method that may be + extended by inheriting classes + * Chars array is cached for better performance + +### 1.8.1 (2015-01-08) + + * Optimized chars() + * Added "ä Ä Ö Ü"" in toAscii() + * Added support for Unicode spaces in toAscii() + * Replaced instances of self::create() with static::create() + * Added missing test cases for safeTruncate() and longestCommonSuffix() + * Updated Stringy\create() to avoid collision when it already exists + +### 1.8.0 (2015-01-03) + + * Listed ext-mbstring in composer.json + * Added Stringy\create function for PHP 5.6 + +### 1.7.0 (2014-10-14) + + * Added containsAll and containsAny + * Light cleanup + +### 1.6.0 (2014-09-14) + + * Added toTitleCase + +### 1.5.2 (2014-07-09) + + * Announced support for HHVM + +### 1.5.1 (2014-04-19) + + * Fixed toAscii() failing to remove remaining non-ascii characters + * Updated slugify() to treat dash and underscore as delimiters by default + * Updated slugify() to remove leading and trailing delimiter, if present + +### 1.5.0 (2014-03-19) + + * Made both str and encoding protected, giving property access to subclasses + * Added getEncoding() + * Fixed isJSON() giving false negatives + * Cleaned up and simplified: replace(), collapseWhitespace(), underscored(), + dasherize(), pad(), padLeft(), padRight() and padBoth() + * Fixed handling consecutive invalid chars in slugify() + * Removed conflicting hard sign transliteration in toAscii() + +### 1.4.0 (2014-02-12) + + * Implemented the IteratorAggregate interface, added chars() + * Renamed count() to countSubstr() + * Updated count() to implement Countable interface + * Implemented the ArrayAccess interface with positive and negative indices + * Switched from PSR-0 to PSR-4 autoloading + +### 1.3.0 (2013-12-16) + + * Additional Bulgarian support for toAscii + * str property made private + * Constructor casts first argument to string + * Constructor throws an InvalidArgumentException when given an array + * Constructor throws an InvalidArgumentException when given an object without + a __toString method + +### 1.2.2 (2013-12-04) + + * Updated create function to use late static binding + * Added optional $replacement param to slugify + +### 1.2.1 (2013-10-11) + + * Cleaned up tests + * Added homepage to composer.json + +### 1.2.0 (2013-09-15) + + * Fixed pad's use of InvalidArgumentException + * Fixed replace(). It now correctly treats regex special chars as normal chars + * Added additional Cyrillic letters to toAscii + * Added $caseSensitive to contains() and count() + * Added toLowerCase() + * Added toUpperCase() + * Added regexReplace() + +### 1.1.0 (2013-08-31) + + * Fix for collapseWhitespace() + * Added isHexadecimal() + * Added constructor to Stringy\Stringy + * Added isSerialized() + * Added isJson() + +### 1.0.0 (2013-08-1) + + * 1.0.0 release + * Added test coverage for Stringy::create and method chaining + * Added tests for returned type + * Fixed StaticStringy::replace(). It was returning a Stringy object instead of string + * Renamed standardize() to the more appropriate toAscii() + * Cleaned up comments and README + +### 1.0.0-rc.1 (2013-07-28) + + * Release candidate diff --git a/vendor/danielstjules/stringy/LICENSE.txt b/vendor/danielstjules/stringy/LICENSE.txt new file mode 100644 index 0000000..0b70302 --- /dev/null +++ b/vendor/danielstjules/stringy/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (C) 2013 Daniel St. Jules + +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/danielstjules/stringy/README.md b/vendor/danielstjules/stringy/README.md new file mode 100644 index 0000000..df48e39 --- /dev/null +++ b/vendor/danielstjules/stringy/README.md @@ -0,0 +1,1082 @@ +![Stringy](http://danielstjules.com/github/stringy-logo.png) + +A PHP string manipulation library with multibyte support. Compatible with PHP +5.4+, PHP 7+, and HHVM. + +``` php +s('string')->toTitleCase()->ensureRight('y') == 'Stringy' +``` + +Refer to the [1.x branch](https://github.com/danielstjules/Stringy/tree/1.x) or +[2.x branch](https://github.com/danielstjules/Stringy/tree/2.x) for older +documentation. + +[![Build Status](https://api.travis-ci.org/danielstjules/Stringy.svg?branch=master)](https://travis-ci.org/danielstjules/Stringy) +[![Total Downloads](https://poser.pugx.org/danielstjules/stringy/downloads)](https://packagist.org/packages/danielstjules/stringy) +[![License](https://poser.pugx.org/danielstjules/stringy/license)](https://packagist.org/packages/danielstjules/stringy) + +* [Why?](#why) +* [Installation](#installation) +* [OO and Chaining](#oo-and-chaining) +* [Implemented Interfaces](#implemented-interfaces) +* [PHP 5.6 Creation](#php-56-creation) +* [StaticStringy](#staticstringy) +* [Class methods](#class-methods) + * [create](#createmixed-str--encoding-) +* [Instance methods](#instance-methods) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
appendatbetweencamelize
charscollapseWhitespacecontainscontainsAll
containsAnycountSubstrdasherizedelimit
endsWithendsWithAnyensureLeftensureRight
firstgetEncodinghasLowerCasehasUpperCase
htmlDecodehtmlEncodehumanizeindexOf
indexOfLastinsertisAlphaisAlphanumeric
isBase64isBlankisHexadecimalisJson
isLowerCaseisSerializedisUpperCaselast
lengthlineslongestCommonPrefixlongestCommonSuffix
longestCommonSubstringlowerCaseFirstpadpadBoth
padLeftpadRightprependregexReplace
removeLeftremoveRightrepeatreplace
reversesafeTruncateshuffleslugify
slicesplitstartsWithstartsWithAny
stripWhitespacesubstrsurroundswapCase
tidytitleizetoAsciitoBoolean
toLowerCasetoSpacestoTabstoTitleCase
toUpperCasetrimtrimLefttrimRight
truncateunderscoredupperCamelizeupperCaseFirst
+ +* [Extensions](#extensions) +* [Tests](#tests) +* [License](#license) + +## Why? + +In part due to a lack of multibyte support (including UTF-8) across many of +PHP's standard string functions. But also to offer an OO wrapper around the +`mbstring` module's multibyte-compatible functions. Stringy handles some quirks, +provides additional functionality, and hopefully makes strings a little easier +to work with! + +```php +// Standard library +strtoupper('fòôbàř'); // 'FòôBàř' +strlen('fòôbàř'); // 10 + +// mbstring +mb_strtoupper('fòôbàř'); // 'FÒÔBÀŘ' +mb_strlen('fòôbàř'); // '6' + +// Stringy +s('fòôbàř')->toUpperCase(); // 'FÒÔBÀŘ' +s('fòôbàř')->length(); // '6' +``` + +## Installation + +If you're using Composer to manage dependencies, you can include the following +in your composer.json file: + +```json +"require": { + "danielstjules/stringy": "~3.1.0" +} +``` + +Then, after running `composer update` or `php composer.phar update`, you can +load the class using Composer's autoloading: + +```php +require 'vendor/autoload.php'; +``` + +Otherwise, you can simply require the file directly: + +```php +require_once 'path/to/Stringy/src/Stringy.php'; +``` + +And in either case, I'd suggest using an alias. + +```php +use Stringy\Stringy as S; +``` + +Please note that Stringy relies on the `mbstring` module for its underlying +multibyte support. If the module is not found, Stringy will use +[symfony/polyfill-mbstring](https://github.com/symfony/polyfill-mbstring). +ex-mbstring is a non-default, but very common module. For example, with debian +and ubuntu, it's included in libapache2-mod-php5, php5-cli, and php5-fpm. For +OSX users, it's a default for any version of PHP installed with homebrew. +If compiling PHP from scratch, it can be included with the +`--enable-mbstring` flag. + +## OO and Chaining + +The library offers OO method chaining, as seen below: + +```php +use Stringy\Stringy as S; +echo S::create('fòô bàř')->collapseWhitespace()->swapCase(); // 'FÒÔ BÀŘ' +``` + +`Stringy\Stringy` has a __toString() method, which returns the current string +when the object is used in a string context, ie: +`(string) S::create('foo') // 'foo'` + +## Implemented Interfaces + +`Stringy\Stringy` implements the `IteratorAggregate` interface, meaning that +`foreach` can be used with an instance of the class: + +``` php +$stringy = S::create('fòôbàř'); +foreach ($stringy as $char) { + echo $char; +} +// 'fòôbàř' +``` + +It implements the `Countable` interface, enabling the use of `count()` to +retrieve the number of characters in the string: + +``` php +$stringy = S::create('fòô'); +count($stringy); // 3 +``` + +Furthermore, the `ArrayAccess` interface has been implemented. As a result, +`isset()` can be used to check if a character at a specific index exists. And +since `Stringy\Stringy` is immutable, any call to `offsetSet` or `offsetUnset` +will throw an exception. `offsetGet` has been implemented, however, and accepts +both positive and negative indexes. Invalid indexes result in an +`OutOfBoundsException`. + +``` php +$stringy = S::create('bàř'); +echo $stringy[2]; // 'ř' +echo $stringy[-2]; // 'à' +isset($stringy[-4]); // false + +$stringy[3]; // OutOfBoundsException +$stringy[2] = 'a'; // Exception +``` + +## PHP 5.6 Creation + +As of PHP 5.6, [`use function`](https://wiki.php.net/rfc/use_function) is +available for importing functions. Stringy exposes a namespaced function, +`Stringy\create`, which emits the same behaviour as `Stringy\Stringy::create()`. +If running PHP 5.6, or another runtime that supports the `use function` syntax, +you can take advantage of an even simpler API as seen below: + +``` php +use function Stringy\create as s; + +// Instead of: S::create('fòô bàř') +s('fòô bàř')->collapseWhitespace()->swapCase(); +``` + +## StaticStringy + +All methods listed under "Instance methods" are available as part of a static +wrapper. For StaticStringy methods, the optional encoding is expected to be the +last argument. The return value is not cast, and may thus be of type Stringy, +integer, boolean, etc. + +```php +use Stringy\StaticStringy as S; + +// Translates to Stringy::create('fòôbàř')->slice(0, 3); +// Returns a Stringy object with the string "fòô" +S::slice('fòôbàř', 0, 3); +``` + +## Class methods + +##### create(mixed $str [, $encoding ]) + +Creates a Stringy object and assigns both str and encoding properties +the supplied values. $str is cast to a string prior to assignment, and if +$encoding is not specified, it defaults to mb_internal_encoding(). It +then returns the initialized object. Throws an InvalidArgumentException +if the first argument is an array or object without a __toString method. + +```php +$stringy = S::create('fòôbàř'); // 'fòôbàř' +``` + +## Instance Methods + +Stringy objects are immutable. All examples below make use of PHP 5.6 +function importing, and PHP 5.4 short array syntax. They also assume the +encoding returned by mb_internal_encoding() is UTF-8. For further details, +see the documentation for the create method above, as well as the notes +on PHP 5.6 creation. + +##### append(string $string) + +Returns a new string with $string appended. + +```php +s('fòô')->append('bàř'); // 'fòôbàř' +``` + +##### at(int $index) + +Returns the character at $index, with indexes starting at 0. + +```php +s('fòôbàř')->at(3); // 'b' +``` + +##### between(string $start, string $end [, int $offset]) + +Returns the substring between $start and $end, if found, or an empty +string. An optional offset may be supplied from which to begin the +search for the start string. + +```php +s('{foo} and {bar}')->between('{', '}'); // 'foo' +``` + +##### camelize() + +Returns a camelCase version of the string. Trims surrounding spaces, +capitalizes letters following digits, spaces, dashes and underscores, +and removes spaces, dashes, as well as underscores. + +```php +s('Camel-Case')->camelize(); // 'camelCase' +``` + +##### chars() + +Returns an array consisting of the characters in the string. + +```php +s('fòôbàř')->chars(); // ['f', 'ò', 'ô', 'b', 'à', 'ř'] +``` + +##### collapseWhitespace() + +Trims the string and replaces consecutive whitespace characters with a +single space. This includes tabs and newline characters, as well as +multibyte whitespace such as the thin space and ideographic space. + +```php +s(' Ο συγγραφέας ')->collapseWhitespace(); // 'Ο συγγραφέας' +``` + +##### contains(string $needle [, boolean $caseSensitive = true ]) + +Returns true if the string contains $needle, false otherwise. By default, +the comparison is case-sensitive, but can be made insensitive +by setting $caseSensitive to false. + +```php +s('Ο συγγραφέας είπε')->contains('συγγραφέας'); // true +``` + +##### containsAll(array $needles [, boolean $caseSensitive = true ]) + +Returns true if the string contains all $needles, false otherwise. By +default the comparison is case-sensitive, but can be made insensitive by +setting $caseSensitive to false. + +```php +s('foo & bar')->containsAll(['foo', 'bar']); // true +``` + +##### containsAny(array $needles [, boolean $caseSensitive = true ]) + +Returns true if the string contains any $needles, false otherwise. By +default the comparison is case-sensitive, but can be made insensitive by +setting $caseSensitive to false. + +```php +s('str contains foo')->containsAny(['foo', 'bar']); // true +``` + +##### countSubstr(string $substring [, boolean $caseSensitive = true ]) + +Returns the number of occurrences of $substring in the given string. +By default, the comparison is case-sensitive, but can be made insensitive +by setting $caseSensitive to false. + +```php +s('Ο συγγραφέας είπε')->countSubstr('α'); // 2 +``` + +##### dasherize() + +Returns a lowercase and trimmed string separated by dashes. Dashes are +inserted before uppercase characters (with the exception of the first +character of the string), and in place of spaces as well as underscores. + +```php +s('fooBar')->dasherize(); // 'foo-bar' +``` + +##### delimit(int $delimiter) + +Returns a lowercase and trimmed string separated by the given delimiter. +Delimiters are inserted before uppercase characters (with the exception +of the first character of the string), and in place of spaces, dashes, +and underscores. Alpha delimiters are not converted to lowercase. + +```php +s('fooBar')->delimit('::'); // 'foo::bar' +``` + +##### endsWith(string $substring [, boolean $caseSensitive = true ]) + +Returns true if the string ends with $substring, false otherwise. By +default, the comparison is case-sensitive, but can be made insensitive by +setting $caseSensitive to false. + +```php +s('fòôbàř')->endsWith('bàř'); // true +``` + +##### endsWithAny(string[] $substrings [, boolean $caseSensitive = true ]) + +Returns true if the string ends with any of $substrings, false otherwise. +By default, the comparison is case-sensitive, but can be made insensitive +by setting $caseSensitive to false. + +```php +s('fòôbàř')->endsWithAny(['bàř', 'baz']); // true +``` + +##### ensureLeft(string $substring) + +Ensures that the string begins with $substring. If it doesn't, it's prepended. + +```php +s('foobar')->ensureLeft('http://'); // 'http://foobar' +``` + +##### ensureRight(string $substring) + +Ensures that the string ends with $substring. If it doesn't, it's appended. + +```php +s('foobar')->ensureRight('.com'); // 'foobar.com' +``` + +##### first(int $n) + +Returns the first $n characters of the string. + +```php +s('fòôbàř')->first(3); // 'fòô' +``` + +##### getEncoding() + +Returns the encoding used by the Stringy object. + +```php +s('fòôbàř')->getEncoding(); // 'UTF-8' +``` + +##### hasLowerCase() + +Returns true if the string contains a lower case char, false otherwise. + +```php +s('fòôbàř')->hasLowerCase(); // true +``` + +##### hasUpperCase() + +Returns true if the string contains an upper case char, false otherwise. + +```php +s('fòôbàř')->hasUpperCase(); // false +``` + +##### htmlDecode() + +Convert all HTML entities to their applicable characters. An alias of +html_entity_decode. For a list of flags, refer to +http://php.net/manual/en/function.html-entity-decode.php + +```php +s('&')->htmlDecode(); // '&' +``` + +##### htmlEncode() + +Convert all applicable characters to HTML entities. An alias of +htmlentities. Refer to http://php.net/manual/en/function.htmlentities.php +for a list of flags. + +```php +s('&')->htmlEncode(); // '&' +``` + +##### humanize() + +Capitalizes the first word of the string, replaces underscores with +spaces, and strips '_id'. + +```php +s('author_id')->humanize(); // 'Author' +``` + +##### indexOf(string $needle [, $offset = 0 ]); + +Returns the index of the first occurrence of $needle in the string, +and false if not found. Accepts an optional offset from which to begin +the search. A negative index searches from the end + +```php +s('string')->indexOf('ing'); // 3 +``` + +##### indexOfLast(string $needle [, $offset = 0 ]); + +Returns the index of the last occurrence of $needle in the string, +and false if not found. Accepts an optional offset from which to begin +the search. Offsets may be negative to count from the last character +in the string. + +```php +s('foobarfoo')->indexOfLast('foo'); // 10 +``` + +##### insert(int $index, string $substring) + +Inserts $substring into the string at the $index provided. + +```php +s('fòôbř')->insert('à', 4); // 'fòôbàř' +``` + +##### isAlpha() + +Returns true if the string contains only alphabetic chars, false otherwise. + +```php +s('丹尼爾')->isAlpha(); // true +``` + +##### isAlphanumeric() + +Returns true if the string contains only alphabetic and numeric chars, false +otherwise. + +```php +s('دانيال1')->isAlphanumeric(); // true +``` + +##### isBase64() + +Returns true if the string is base64 encoded, false otherwise. + +```php +s('Zm9vYmFy')->isBase64(); // true +``` + +##### isBlank() + +Returns true if the string contains only whitespace chars, false otherwise. + +```php +s("\n\t \v\f")->isBlank(); // true +``` + +##### isHexadecimal() + +Returns true if the string contains only hexadecimal chars, false otherwise. + +```php +s('A102F')->isHexadecimal(); // true +``` + +##### isJson() + +Returns true if the string is JSON, false otherwise. Unlike json_decode +in PHP 5.x, this method is consistent with PHP 7 and other JSON parsers, +in that an empty string is not considered valid JSON. + +```php +s('{"foo":"bar"}')->isJson(); // true +``` + +##### isLowerCase() + +Returns true if the string contains only lower case chars, false otherwise. + +```php +s('fòôbàř')->isLowerCase(); // true +``` + +##### isSerialized() + +Returns true if the string is serialized, false otherwise. + +```php +s('a:1:{s:3:"foo";s:3:"bar";}')->isSerialized(); // true +``` + +##### isUpperCase() + +Returns true if the string contains only upper case chars, false otherwise. + +```php +s('FÒÔBÀŘ')->isUpperCase(); // true +``` + +##### last(int $n) + +Returns the last $n characters of the string. + +```php +s('fòôbàř')->last(3); // 'bàř' +``` + +##### length() + +Returns the length of the string. An alias for PHP's mb_strlen() function. + +```php +s('fòôbàř')->length(); // 6 +``` + +##### lines() + +Splits on newlines and carriage returns, returning an array of Stringy +objects corresponding to the lines in the string. + +```php +s("fòô\r\nbàř\n")->lines(); // ['fòô', 'bàř', ''] +``` + +##### longestCommonPrefix(string $otherStr) + +Returns the longest common prefix between the string and $otherStr. + +```php +s('foobar')->longestCommonPrefix('foobaz'); // 'fooba' +``` + +##### longestCommonSuffix(string $otherStr) + +Returns the longest common suffix between the string and $otherStr. + +```php +s('fòôbàř')->longestCommonSuffix('fòrbàř'); // 'bàř' +``` + +##### longestCommonSubstring(string $otherStr) + +Returns the longest common substring between the string and $otherStr. In the +case of ties, it returns that which occurs first. + +```php +s('foobar')->longestCommonSubstring('boofar'); // 'oo' +``` + +##### lowerCaseFirst() + +Converts the first character of the supplied string to lower case. + +```php +s('Σ foo')->lowerCaseFirst(); // 'σ foo' +``` + +##### pad(int $length [, string $padStr = ' ' [, string $padType = 'right' ]]) + +Pads the string to a given length with $padStr. If length is less than +or equal to the length of the string, no padding takes places. The default +string used for padding is a space, and the default type (one of 'left', +'right', 'both') is 'right'. Throws an InvalidArgumentException if +$padType isn't one of those 3 values. + +```php +s('fòôbàř')->pad(9, '-/', 'left'); // '-/-fòôbàř' +``` + +##### padBoth(int $length [, string $padStr = ' ' ]) + +Returns a new string of a given length such that both sides of the string +string are padded. Alias for pad() with a $padType of 'both'. + +```php +s('foo bar')->padBoth(9, ' '); // ' foo bar ' +``` + +##### padLeft(int $length [, string $padStr = ' ' ]) + +Returns a new string of a given length such that the beginning of the +string is padded. Alias for pad() with a $padType of 'left'. + +```php +s('foo bar')->padLeft(9, ' '); // ' foo bar' +``` + +##### padRight(int $length [, string $padStr = ' ' ]) + +Returns a new string of a given length such that the end of the string is +padded. Alias for pad() with a $padType of 'right'. + +```php +s('foo bar')->padRight(10, '_*'); // 'foo bar_*_' +``` + +##### prepend(string $string) + +Returns a new string starting with $string. + +```php +s('bàř')->prepend('fòô'); // 'fòôbàř' +``` + +##### regexReplace(string $pattern, string $replacement [, string $options = 'msr']) + +Replaces all occurrences of $pattern in $str by $replacement. An alias +for mb_ereg_replace(). Note that the 'i' option with multibyte patterns +in mb_ereg_replace() requires PHP 5.6+ for correct results. This is due +to a lack of support in the bundled version of Oniguruma in PHP < 5.6, +and current versions of HHVM (3.8 and below). + +```php +s('fòô ')->regexReplace('f[òô]+\s', 'bàř'); // 'bàř' +s('fò')->regexReplace('(ò)', '\\1ô'); // 'fòô' +``` + +##### removeLeft(string $substring) + +Returns a new string with the prefix $substring removed, if present. + +```php +s('fòôbàř')->removeLeft('fòô'); // 'bàř' +``` + +##### removeRight(string $substring) + +Returns a new string with the suffix $substring removed, if present. + +```php +s('fòôbàř')->removeRight('bàř'); // 'fòô' +``` + +##### repeat(int $multiplier) + +Returns a repeated string given a multiplier. An alias for str_repeat. + +```php +s('α')->repeat(3); // 'ααα' +``` + +##### replace(string $search, string $replacement) + +Replaces all occurrences of $search in $str by $replacement. + +```php +s('fòô bàř fòô bàř')->replace('fòô ', ''); // 'bàř bàř' +``` + +##### reverse() + +Returns a reversed string. A multibyte version of strrev(). + +```php +s('fòôbàř')->reverse(); // 'řàbôòf' +``` + +##### safeTruncate(int $length [, string $substring = '' ]) + +Truncates the string to a given length, while ensuring that it does not +split words. If $substring is provided, and truncating occurs, the +string is further truncated so that the substring may be appended without +exceeding the desired length. + +```php +s('What are your plans today?')->safeTruncate(22, '...'); +// 'What are your plans...' +``` + +##### shuffle() + +A multibyte str_shuffle() function. It returns a string with its characters in +random order. + +```php +s('fòôbàř')->shuffle(); // 'àôřbòf' +``` + +##### slugify([, string $replacement = '-' [, string $language = 'en']]) + +Converts the string into an URL slug. This includes replacing non-ASCII +characters with their closest ASCII equivalents, removing remaining +non-ASCII and non-alphanumeric characters, and replacing whitespace with +$replacement. The replacement defaults to a single dash, and the string +is also converted to lowercase. The language of the source string can +also be supplied for language-specific transliteration. + +```php +s('Using strings like fòô bàř')->slugify(); // 'using-strings-like-foo-bar' +``` + +##### slice(int $start [, int $end ]) + +Returns the substring beginning at $start, and up to, but not including +the index specified by $end. If $end is omitted, the function extracts +the remaining string. If $end is negative, it is computed from the end +of the string. + +```php +s('fòôbàř')->slice(3, -1); // 'bà' +``` + +##### split(string $pattern [, int $limit ]) + +Splits the string with the provided regular expression, returning an +array of Stringy objects. An optional integer $limit will truncate the +results. + +```php +s('foo,bar,baz')->split(',', 2); // ['foo', 'bar'] +``` + +##### startsWith(string $substring [, boolean $caseSensitive = true ]) + +Returns true if the string begins with $substring, false otherwise. +By default, the comparison is case-sensitive, but can be made insensitive +by setting $caseSensitive to false. + +```php +s('FÒÔbàřbaz')->startsWith('fòôbàř', false); // true +``` + +##### startsWithAny(string[] $substrings [, boolean $caseSensitive = true ]) + +Returns true if the string begins with any of $substrings, false +otherwise. By default the comparison is case-sensitive, but can be made +insensitive by setting $caseSensitive to false. + +```php +s('FÒÔbàřbaz')->startsWithAny(['fòô', 'bàř'], false); // true +``` + +##### stripWhitespace() + +Strip all whitespace characters. This includes tabs and newline +characters, as well as multibyte whitespace such as the thin space +and ideographic space. + +```php +s(' Ο συγγραφέας ')->stripWhitespace(); // 'Οσυγγραφέας' +``` + +##### substr(int $start [, int $length ]) + +Returns the substring beginning at $start with the specified $length. +It differs from the mb_substr() function in that providing a $length of +null will return the rest of the string, rather than an empty string. + +```php +s('fòôbàř')->substr(2, 3); // 'ôbà' +``` + +##### surround(string $substring) + +Surrounds a string with the given substring. + +```php +s(' ͜ ')->surround('ʘ'); // 'ʘ ͜ ʘ' +``` + +##### swapCase() + +Returns a case swapped version of the string. + +```php +s('Ντανιλ')->swapCase(); // 'νΤΑΝΙΛ' +``` + +##### tidy() + +Returns a string with smart quotes, ellipsis characters, and dashes from +Windows-1252 (commonly used in Word documents) replaced by their ASCII equivalents. + +```php +s('“I see…”')->tidy(); // '"I see..."' +``` + +##### titleize([, array $ignore]) + +Returns a trimmed string with the first letter of each word capitalized. +Also accepts an array, $ignore, allowing you to list words not to be +capitalized. + +```php +$ignore = ['at', 'by', 'for', 'in', 'of', 'on', 'out', 'to', 'the']; +s('i like to watch television')->titleize($ignore); +// 'I Like to Watch Television' +``` + +##### toAscii([, string $language = 'en' [, bool $removeUnsupported = true ]]) + +Returns an ASCII version of the string. A set of non-ASCII characters are +replaced with their closest ASCII counterparts, and the rest are removed +by default. The language or locale of the source string can be supplied +for language-specific transliteration in any of the following formats: +en, en_GB, or en-GB. For example, passing "de" results in "äöü" mapping +to "aeoeue" rather than "aou" as in other languages. + +```php +s('fòôbàř')->toAscii(); // 'foobar' +s('äöü')->toAscii(); // 'aou' +s('äöü')->toAscii('de'); // 'aeoeue' +``` + +##### toBoolean() + +Returns a boolean representation of the given logical string value. +For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0', +'off', and 'no' will return false. In all instances, case is ignored. +For other numeric strings, their sign will determine the return value. +In addition, blank strings consisting of only whitespace will return +false. For all other strings, the return value is a result of a +boolean cast. + +```php +s('OFF')->toBoolean(); // false +``` + +##### toLowerCase() + +Converts all characters in the string to lowercase. An alias for PHP's +mb_strtolower(). + +```php +s('FÒÔBÀŘ')->toLowerCase(); // 'fòôbàř' +``` + +##### toSpaces([, tabLength = 4 ]) + +Converts each tab in the string to some number of spaces, as defined by +$tabLength. By default, each tab is converted to 4 consecutive spaces. + +```php +s(' String speech = "Hi"')->toSpaces(); // ' String speech = "Hi"' +``` + +##### toTabs([, tabLength = 4 ]) + +Converts each occurrence of some consecutive number of spaces, as defined +by $tabLength, to a tab. By default, each 4 consecutive spaces are +converted to a tab. + +```php +s(' fòô bàř')->toTabs(); +// ' fòô bàř' +``` + +##### toTitleCase() + +Converts the first character of each word in the string to uppercase. + +```php +s('fòô bàř')->toTitleCase(); // 'Fòô Bàř' +``` + +##### toUpperCase() + +Converts all characters in the string to uppercase. An alias for PHP's +mb_strtoupper(). + +```php +s('fòôbàř')->toUpperCase(); // 'FÒÔBÀŘ' +``` + +##### trim([, string $chars]) + +Returns a string with whitespace removed from the start and end of the +string. Supports the removal of unicode whitespace. Accepts an optional +string of characters to strip instead of the defaults. + +```php +s(' fòôbàř ')->trim(); // 'fòôbàř' +``` + +##### trimLeft([, string $chars]) + +Returns a string with whitespace removed from the start of the string. +Supports the removal of unicode whitespace. Accepts an optional +string of characters to strip instead of the defaults. + +```php +s(' fòôbàř ')->trimLeft(); // 'fòôbàř ' +``` + +##### trimRight([, string $chars]) + +Returns a string with whitespace removed from the end of the string. +Supports the removal of unicode whitespace. Accepts an optional +string of characters to strip instead of the defaults. + +```php +s(' fòôbàř ')->trimRight(); // ' fòôbàř' +``` + +##### truncate(int $length [, string $substring = '' ]) + +Truncates the string to a given length. If $substring is provided, and +truncating occurs, the string is further truncated so that the substring +may be appended without exceeding the desired length. + +```php +s('What are your plans today?')->truncate(19, '...'); // 'What are your pl...' +``` + +##### underscored() + +Returns a lowercase and trimmed string separated by underscores. +Underscores are inserted before uppercase characters (with the exception +of the first character of the string), and in place of spaces as well as dashes. + +```php +s('TestUCase')->underscored(); // 'test_u_case' +``` + +##### upperCamelize() + +Returns an UpperCamelCase version of the supplied string. It trims +surrounding spaces, capitalizes letters following digits, spaces, dashes +and underscores, and removes spaces, dashes, underscores. + +```php +s('Upper Camel-Case')->upperCamelize(); // 'UpperCamelCase' +``` + +##### upperCaseFirst() + +Converts the first character of the supplied string to upper case. + +```php +s('σ foo')->upperCaseFirst(); // 'Σ foo' +``` + +## Extensions + +The following is a list of libraries that extend Stringy: + + * [SliceableStringy](https://github.com/danielstjules/SliceableStringy): +Python-like string slices in PHP + * [SubStringy](https://github.com/TCB13/SubStringy): +Advanced substring methods + +## Tests + +From the project directory, tests can be ran using `phpunit` + +## License + +Released under the MIT License - see `LICENSE.txt` for details. diff --git a/vendor/danielstjules/stringy/composer.json b/vendor/danielstjules/stringy/composer.json new file mode 100644 index 0000000..092989f --- /dev/null +++ b/vendor/danielstjules/stringy/composer.json @@ -0,0 +1,35 @@ +{ + "name": "danielstjules/stringy", + "description": "A string manipulation library with multibyte support", + "keywords": [ + "multibyte", "string", "manipulation", "utility", "methods", "utf-8", + "helpers", "utils", "utf" + ], + "homepage": "https://github.com/danielstjules/Stringy", + "license": "MIT", + "authors": [ + { + "name": "Daniel St. Jules", + "email": "danielst.jules@gmail.com", + "homepage": "http://www.danielstjules.com" + } + ], + "require": { + "php": ">=5.4.0", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "support": { + "issues": "https://github.com/danielstjules/Stringy/issues", + "source": "https://github.com/danielstjules/Stringy" + }, + "autoload": { + "psr-4": { "Stringy\\": "src/" }, + "files": ["src/Create.php"] + }, + "autoload-dev": { + "classmap": [ "tests" ] + } +} diff --git a/vendor/danielstjules/stringy/src/Create.php b/vendor/danielstjules/stringy/src/Create.php new file mode 100644 index 0000000..c6a2f44 --- /dev/null +++ b/vendor/danielstjules/stringy/src/Create.php @@ -0,0 +1,19 @@ +slice(0, 3); + * The result is not cast, so the return value may be of type Stringy, + * integer, boolean, etc. + * + * @param string $name + * @param mixed[] $arguments + * + * @return Stringy + * + * @throws \BadMethodCallException + */ + public static function __callStatic($name, $arguments) + { + if (!static::$methodArgs) { + $stringyClass = new ReflectionClass('Stringy\Stringy'); + $methods = $stringyClass->getMethods(ReflectionMethod::IS_PUBLIC); + + foreach ($methods as $method) { + $params = $method->getNumberOfParameters() + 2; + static::$methodArgs[$method->name] = $params; + } + } + + if (!isset(static::$methodArgs[$name])) { + throw new BadMethodCallException($name . ' is not a valid method'); + } + + $numArgs = count($arguments); + $str = ($numArgs) ? $arguments[0] : ''; + + if ($numArgs === static::$methodArgs[$name]) { + $args = array_slice($arguments, 1, -1); + $encoding = $arguments[$numArgs - 1]; + } else { + $args = array_slice($arguments, 1); + $encoding = null; + } + + $stringy = Stringy::create($str, $encoding); + + $result = call_user_func_array([$stringy, $name], $args); + + $cast = function($val) { + if (is_object($val) && $val instanceof Stringy) { + return (string) $val; + } else { + return $val; + } + }; + + return is_array($result) ? array_map($cast, $result) : $cast($result); + } +} diff --git a/vendor/danielstjules/stringy/src/Stringy.php b/vendor/danielstjules/stringy/src/Stringy.php new file mode 100644 index 0000000..ccb6f5a --- /dev/null +++ b/vendor/danielstjules/stringy/src/Stringy.php @@ -0,0 +1,1986 @@ +str = (string) $str; + $this->encoding = $encoding ?: \mb_internal_encoding(); + } + + /** + * Creates a Stringy object and assigns both str and encoding properties + * the supplied values. $str is cast to a string prior to assignment, and if + * $encoding is not specified, it defaults to mb_internal_encoding(). It + * then returns the initialized object. Throws an InvalidArgumentException + * if the first argument is an array or object without a __toString method. + * + * @param mixed $str Value to modify, after being cast to string + * @param string $encoding The character encoding + * @return static A Stringy object + * @throws \InvalidArgumentException if an array or object without a + * __toString method is passed as the first argument + */ + public static function create($str = '', $encoding = null) + { + return new static($str, $encoding); + } + + /** + * Returns the value in $str. + * + * @return string The current value of the $str property + */ + public function __toString() + { + return $this->str; + } + + /** + * Returns a new string with $string appended. + * + * @param string $string The string to append + * @return static Object with appended $string + */ + public function append($string) + { + return static::create($this->str . $string, $this->encoding); + } + + /** + * Returns the character at $index, with indexes starting at 0. + * + * @param int $index Position of the character + * @return static The character at $index + */ + public function at($index) + { + return $this->substr($index, 1); + } + + /** + * Returns the substring between $start and $end, if found, or an empty + * string. An optional offset may be supplied from which to begin the + * search for the start string. + * + * @param string $start Delimiter marking the start of the substring + * @param string $end Delimiter marking the end of the substring + * @param int $offset Index from which to begin the search + * @return static Object whose $str is a substring between $start and $end + */ + public function between($start, $end, $offset = 0) + { + $startIndex = $this->indexOf($start, $offset); + if ($startIndex === false) { + return static::create('', $this->encoding); + } + + $substrIndex = $startIndex + \mb_strlen($start, $this->encoding); + $endIndex = $this->indexOf($end, $substrIndex); + if ($endIndex === false) { + return static::create('', $this->encoding); + } + + return $this->substr($substrIndex, $endIndex - $substrIndex); + } + + /** + * Returns a camelCase version of the string. Trims surrounding spaces, + * capitalizes letters following digits, spaces, dashes and underscores, + * and removes spaces, dashes, as well as underscores. + * + * @return static Object with $str in camelCase + */ + public function camelize() + { + $encoding = $this->encoding; + $stringy = $this->trim()->lowerCaseFirst(); + $stringy->str = preg_replace('/^[-_]+/', '', $stringy->str); + + $stringy->str = preg_replace_callback( + '/[-_\s]+(.)?/u', + function ($match) use ($encoding) { + if (isset($match[1])) { + return \mb_strtoupper($match[1], $encoding); + } + + return ''; + }, + $stringy->str + ); + + $stringy->str = preg_replace_callback( + '/[\d]+(.)?/u', + function ($match) use ($encoding) { + return \mb_strtoupper($match[0], $encoding); + }, + $stringy->str + ); + + return $stringy; + } + + /** + * Returns an array consisting of the characters in the string. + * + * @return array An array of string chars + */ + public function chars() + { + $chars = []; + for ($i = 0, $l = $this->length(); $i < $l; $i++) { + $chars[] = $this->at($i)->str; + } + + return $chars; + } + + /** + * Trims the string and replaces consecutive whitespace characters with a + * single space. This includes tabs and newline characters, as well as + * multibyte whitespace such as the thin space and ideographic space. + * + * @return static Object with a trimmed $str and condensed whitespace + */ + public function collapseWhitespace() + { + return $this->regexReplace('[[:space:]]+', ' ')->trim(); + } + + /** + * Returns true if the string contains $needle, false otherwise. By default + * the comparison is case-sensitive, but can be made insensitive by setting + * $caseSensitive to false. + * + * @param string $needle Substring to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @return bool Whether or not $str contains $needle + */ + public function contains($needle, $caseSensitive = true) + { + $encoding = $this->encoding; + + if ($caseSensitive) { + return (\mb_strpos($this->str, $needle, 0, $encoding) !== false); + } + + return (\mb_stripos($this->str, $needle, 0, $encoding) !== false); + } + + /** + * Returns true if the string contains all $needles, false otherwise. By + * default the comparison is case-sensitive, but can be made insensitive by + * setting $caseSensitive to false. + * + * @param string[] $needles Substrings to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @return bool Whether or not $str contains $needle + */ + public function containsAll($needles, $caseSensitive = true) + { + if (empty($needles)) { + return false; + } + + foreach ($needles as $needle) { + if (!$this->contains($needle, $caseSensitive)) { + return false; + } + } + + return true; + } + + /** + * Returns true if the string contains any $needles, false otherwise. By + * default the comparison is case-sensitive, but can be made insensitive by + * setting $caseSensitive to false. + * + * @param string[] $needles Substrings to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @return bool Whether or not $str contains $needle + */ + public function containsAny($needles, $caseSensitive = true) + { + if (empty($needles)) { + return false; + } + + foreach ($needles as $needle) { + if ($this->contains($needle, $caseSensitive)) { + return true; + } + } + + return false; + } + + /** + * Returns the length of the string, implementing the countable interface. + * + * @return int The number of characters in the string, given the encoding + */ + public function count() + { + return $this->length(); + } + + /** + * Returns the number of occurrences of $substring in the given string. + * By default, the comparison is case-sensitive, but can be made insensitive + * by setting $caseSensitive to false. + * + * @param string $substring The substring to search for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @return int The number of $substring occurrences + */ + public function countSubstr($substring, $caseSensitive = true) + { + if ($caseSensitive) { + return \mb_substr_count($this->str, $substring, $this->encoding); + } + + $str = \mb_strtoupper($this->str, $this->encoding); + $substring = \mb_strtoupper($substring, $this->encoding); + + return \mb_substr_count($str, $substring, $this->encoding); + } + + /** + * Returns a lowercase and trimmed string separated by dashes. Dashes are + * inserted before uppercase characters (with the exception of the first + * character of the string), and in place of spaces as well as underscores. + * + * @return static Object with a dasherized $str + */ + public function dasherize() + { + return $this->delimit('-'); + } + + /** + * Returns a lowercase and trimmed string separated by the given delimiter. + * Delimiters are inserted before uppercase characters (with the exception + * of the first character of the string), and in place of spaces, dashes, + * and underscores. Alpha delimiters are not converted to lowercase. + * + * @param string $delimiter Sequence used to separate parts of the string + * @return static Object with a delimited $str + */ + public function delimit($delimiter) + { + $regexEncoding = $this->regexEncoding(); + $this->regexEncoding($this->encoding); + + $str = $this->eregReplace('\B([A-Z])', '-\1', $this->trim()); + $str = \mb_strtolower($str, $this->encoding); + $str = $this->eregReplace('[-_\s]+', $delimiter, $str); + + $this->regexEncoding($regexEncoding); + + return static::create($str, $this->encoding); + } + + /** + * Returns true if the string ends with $substring, false otherwise. By + * default, the comparison is case-sensitive, but can be made insensitive + * by setting $caseSensitive to false. + * + * @param string $substring The substring to look for + * @param bool $caseSensitive Whether or not to enforce case-sensitivity + * @return bool Whether or not $str ends with $substring + */ + public function endsWith($substring, $caseSensitive = true) + { + $substringLength = \mb_strlen($substring, $this->encoding); + $strLength = $this->length(); + + $endOfStr = \mb_substr($this->str, $strLength - $substringLength, + $substringLength, $this->encoding); + + if (!$caseSensitive) { + $substring = \mb_strtolower($substring, $this->encoding); + $endOfStr = \mb_strtolower($endOfStr, $this->encoding); + } + + return (string) $substring === $endOfStr; + } + + /** + * Returns true if the string ends with any of $substrings, false otherwise. + * By default, the comparison is case-sensitive, but can be made insensitive + * by setting $caseSensitive to false. + * + * @param string[] $substrings Substrings to look for + * @param bool $caseSensitive Whether or not to enforce + * case-sensitivity + * @return bool Whether or not $str ends with $substring + */ + public function endsWithAny($substrings, $caseSensitive = true) + { + if (empty($substrings)) { + return false; + } + + foreach ($substrings as $substring) { + if ($this->endsWith($substring, $caseSensitive)) { + return true; + } + } + + return false; + } + + /** + * Ensures that the string begins with $substring. If it doesn't, it's + * prepended. + * + * @param string $substring The substring to add if not present + * @return static Object with its $str prefixed by the $substring + */ + public function ensureLeft($substring) + { + $stringy = static::create($this->str, $this->encoding); + + if (!$stringy->startsWith($substring)) { + $stringy->str = $substring . $stringy->str; + } + + return $stringy; + } + + /** + * Ensures that the string ends with $substring. If it doesn't, it's + * appended. + * + * @param string $substring The substring to add if not present + * @return static Object with its $str suffixed by the $substring + */ + public function ensureRight($substring) + { + $stringy = static::create($this->str, $this->encoding); + + if (!$stringy->endsWith($substring)) { + $stringy->str .= $substring; + } + + return $stringy; + } + + /** + * Returns the first $n characters of the string. + * + * @param int $n Number of characters to retrieve from the start + * @return static Object with its $str being the first $n chars + */ + public function first($n) + { + $stringy = static::create($this->str, $this->encoding); + + if ($n < 0) { + $stringy->str = ''; + return $stringy; + } + + return $stringy->substr(0, $n); + } + + /** + * Returns the encoding used by the Stringy object. + * + * @return string The current value of the $encoding property + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Returns a new ArrayIterator, thus implementing the IteratorAggregate + * interface. The ArrayIterator's constructor is passed an array of chars + * in the multibyte string. This enables the use of foreach with instances + * of Stringy\Stringy. + * + * @return \ArrayIterator An iterator for the characters in the string + */ + public function getIterator() + { + return new ArrayIterator($this->chars()); + } + + /** + * Returns true if the string contains a lower case char, false + * otherwise. + * + * @return bool Whether or not the string contains a lower case character. + */ + public function hasLowerCase() + { + return $this->matchesPattern('.*[[:lower:]]'); + } + + /** + * Returns true if the string contains an upper case char, false + * otherwise. + * + * @return bool Whether or not the string contains an upper case character. + */ + public function hasUpperCase() + { + return $this->matchesPattern('.*[[:upper:]]'); + } + + + /** + * Convert all HTML entities to their applicable characters. An alias of + * html_entity_decode. For a list of flags, refer to + * http://php.net/manual/en/function.html-entity-decode.php + * + * @param int|null $flags Optional flags + * @return static Object with the resulting $str after being html decoded. + */ + public function htmlDecode($flags = ENT_COMPAT) + { + $str = html_entity_decode($this->str, $flags, $this->encoding); + + return static::create($str, $this->encoding); + } + + /** + * Convert all applicable characters to HTML entities. An alias of + * htmlentities. Refer to http://php.net/manual/en/function.htmlentities.php + * for a list of flags. + * + * @param int|null $flags Optional flags + * @return static Object with the resulting $str after being html encoded. + */ + public function htmlEncode($flags = ENT_COMPAT) + { + $str = htmlentities($this->str, $flags, $this->encoding); + + return static::create($str, $this->encoding); + } + + /** + * Capitalizes the first word of the string, replaces underscores with + * spaces, and strips '_id'. + * + * @return static Object with a humanized $str + */ + public function humanize() + { + $str = str_replace(['_id', '_'], ['', ' '], $this->str); + + return static::create($str, $this->encoding)->trim()->upperCaseFirst(); + } + + /** + * Returns the index of the first occurrence of $needle in the string, + * and false if not found. Accepts an optional offset from which to begin + * the search. + * + * @param string $needle Substring to look for + * @param int $offset Offset from which to search + * @return int|bool The occurrence's index if found, otherwise false + */ + public function indexOf($needle, $offset = 0) + { + return \mb_strpos($this->str, (string) $needle, + (int) $offset, $this->encoding); + } + + /** + * Returns the index of the last occurrence of $needle in the string, + * and false if not found. Accepts an optional offset from which to begin + * the search. Offsets may be negative to count from the last character + * in the string. + * + * @param string $needle Substring to look for + * @param int $offset Offset from which to search + * @return int|bool The last occurrence's index if found, otherwise false + */ + public function indexOfLast($needle, $offset = 0) + { + return \mb_strrpos($this->str, (string) $needle, + (int) $offset, $this->encoding); + } + + /** + * Inserts $substring into the string at the $index provided. + * + * @param string $substring String to be inserted + * @param int $index The index at which to insert the substring + * @return static Object with the resulting $str after the insertion + */ + public function insert($substring, $index) + { + $stringy = static::create($this->str, $this->encoding); + if ($index > $stringy->length()) { + return $stringy; + } + + $start = \mb_substr($stringy->str, 0, $index, $stringy->encoding); + $end = \mb_substr($stringy->str, $index, $stringy->length(), + $stringy->encoding); + + $stringy->str = $start . $substring . $end; + + return $stringy; + } + + /** + * Returns true if the string contains only alphabetic chars, false + * otherwise. + * + * @return bool Whether or not $str contains only alphabetic chars + */ + public function isAlpha() + { + return $this->matchesPattern('^[[:alpha:]]*$'); + } + + /** + * Returns true if the string contains only alphabetic and numeric chars, + * false otherwise. + * + * @return bool Whether or not $str contains only alphanumeric chars + */ + public function isAlphanumeric() + { + return $this->matchesPattern('^[[:alnum:]]*$'); + } + + /** + * Returns true if the string contains only whitespace chars, false + * otherwise. + * + * @return bool Whether or not $str contains only whitespace characters + */ + public function isBlank() + { + return $this->matchesPattern('^[[:space:]]*$'); + } + + /** + * Returns true if the string contains only hexadecimal chars, false + * otherwise. + * + * @return bool Whether or not $str contains only hexadecimal chars + */ + public function isHexadecimal() + { + return $this->matchesPattern('^[[:xdigit:]]*$'); + } + + /** + * Returns true if the string is JSON, false otherwise. Unlike json_decode + * in PHP 5.x, this method is consistent with PHP 7 and other JSON parsers, + * in that an empty string is not considered valid JSON. + * + * @return bool Whether or not $str is JSON + */ + public function isJson() + { + if (!$this->length()) { + return false; + } + + json_decode($this->str); + + return (json_last_error() === JSON_ERROR_NONE); + } + + /** + * Returns true if the string contains only lower case chars, false + * otherwise. + * + * @return bool Whether or not $str contains only lower case characters + */ + public function isLowerCase() + { + return $this->matchesPattern('^[[:lower:]]*$'); + } + + /** + * Returns true if the string is serialized, false otherwise. + * + * @return bool Whether or not $str is serialized + */ + public function isSerialized() + { + return $this->str === 'b:0;' || @unserialize($this->str) !== false; + } + + + /** + * Returns true if the string is base64 encoded, false otherwise. + * + * @return bool Whether or not $str is base64 encoded + */ + public function isBase64() + { + return (base64_encode(base64_decode($this->str, true)) === $this->str); + } + + /** + * Returns true if the string contains only lower case chars, false + * otherwise. + * + * @return bool Whether or not $str contains only lower case characters + */ + public function isUpperCase() + { + return $this->matchesPattern('^[[:upper:]]*$'); + } + + /** + * Returns the last $n characters of the string. + * + * @param int $n Number of characters to retrieve from the end + * @return static Object with its $str being the last $n chars + */ + public function last($n) + { + $stringy = static::create($this->str, $this->encoding); + + if ($n <= 0) { + $stringy->str = ''; + return $stringy; + } + + return $stringy->substr(-$n); + } + + /** + * Returns the length of the string. An alias for PHP's mb_strlen() function. + * + * @return int The number of characters in $str given the encoding + */ + public function length() + { + return \mb_strlen($this->str, $this->encoding); + } + + /** + * Splits on newlines and carriage returns, returning an array of Stringy + * objects corresponding to the lines in the string. + * + * @return static[] An array of Stringy objects + */ + public function lines() + { + $array = $this->split('[\r\n]{1,2}', $this->str); + for ($i = 0; $i < count($array); $i++) { + $array[$i] = static::create($array[$i], $this->encoding); + } + + return $array; + } + + /** + * Returns the longest common prefix between the string and $otherStr. + * + * @param string $otherStr Second string for comparison + * @return static Object with its $str being the longest common prefix + */ + public function longestCommonPrefix($otherStr) + { + $encoding = $this->encoding; + $maxLength = min($this->length(), \mb_strlen($otherStr, $encoding)); + + $longestCommonPrefix = ''; + for ($i = 0; $i < $maxLength; $i++) { + $char = \mb_substr($this->str, $i, 1, $encoding); + + if ($char == \mb_substr($otherStr, $i, 1, $encoding)) { + $longestCommonPrefix .= $char; + } else { + break; + } + } + + return static::create($longestCommonPrefix, $encoding); + } + + /** + * Returns the longest common suffix between the string and $otherStr. + * + * @param string $otherStr Second string for comparison + * @return static Object with its $str being the longest common suffix + */ + public function longestCommonSuffix($otherStr) + { + $encoding = $this->encoding; + $maxLength = min($this->length(), \mb_strlen($otherStr, $encoding)); + + $longestCommonSuffix = ''; + for ($i = 1; $i <= $maxLength; $i++) { + $char = \mb_substr($this->str, -$i, 1, $encoding); + + if ($char == \mb_substr($otherStr, -$i, 1, $encoding)) { + $longestCommonSuffix = $char . $longestCommonSuffix; + } else { + break; + } + } + + return static::create($longestCommonSuffix, $encoding); + } + + /** + * Returns the longest common substring between the string and $otherStr. + * In the case of ties, it returns that which occurs first. + * + * @param string $otherStr Second string for comparison + * @return static Object with its $str being the longest common substring + */ + public function longestCommonSubstring($otherStr) + { + // Uses dynamic programming to solve + // http://en.wikipedia.org/wiki/Longest_common_substring_problem + $encoding = $this->encoding; + $stringy = static::create($this->str, $encoding); + $strLength = $stringy->length(); + $otherLength = \mb_strlen($otherStr, $encoding); + + // Return if either string is empty + if ($strLength == 0 || $otherLength == 0) { + $stringy->str = ''; + return $stringy; + } + + $len = 0; + $end = 0; + $table = array_fill(0, $strLength + 1, + array_fill(0, $otherLength + 1, 0)); + + for ($i = 1; $i <= $strLength; $i++) { + for ($j = 1; $j <= $otherLength; $j++) { + $strChar = \mb_substr($stringy->str, $i - 1, 1, $encoding); + $otherChar = \mb_substr($otherStr, $j - 1, 1, $encoding); + + if ($strChar == $otherChar) { + $table[$i][$j] = $table[$i - 1][$j - 1] + 1; + if ($table[$i][$j] > $len) { + $len = $table[$i][$j]; + $end = $i; + } + } else { + $table[$i][$j] = 0; + } + } + } + + $stringy->str = \mb_substr($stringy->str, $end - $len, $len, $encoding); + + return $stringy; + } + + /** + * Converts the first character of the string to lower case. + * + * @return static Object with the first character of $str being lower case + */ + public function lowerCaseFirst() + { + $first = \mb_substr($this->str, 0, 1, $this->encoding); + $rest = \mb_substr($this->str, 1, $this->length() - 1, + $this->encoding); + + $str = \mb_strtolower($first, $this->encoding) . $rest; + + return static::create($str, $this->encoding); + } + + /** + * Returns whether or not a character exists at an index. Offsets may be + * negative to count from the last character in the string. Implements + * part of the ArrayAccess interface. + * + * @param mixed $offset The index to check + * @return boolean Whether or not the index exists + */ + public function offsetExists($offset) + { + $length = $this->length(); + $offset = (int) $offset; + + if ($offset >= 0) { + return ($length > $offset); + } + + return ($length >= abs($offset)); + } + + /** + * Returns the character at the given index. Offsets may be negative to + * count from the last character in the string. Implements part of the + * ArrayAccess interface, and throws an OutOfBoundsException if the index + * does not exist. + * + * @param mixed $offset The index from which to retrieve the char + * @return mixed The character at the specified index + * @throws \OutOfBoundsException If the positive or negative offset does + * not exist + */ + public function offsetGet($offset) + { + $offset = (int) $offset; + $length = $this->length(); + + if (($offset >= 0 && $length <= $offset) || $length < abs($offset)) { + throw new OutOfBoundsException('No character exists at the index'); + } + + return \mb_substr($this->str, $offset, 1, $this->encoding); + } + + /** + * Implements part of the ArrayAccess interface, but throws an exception + * when called. This maintains the immutability of Stringy objects. + * + * @param mixed $offset The index of the character + * @param mixed $value Value to set + * @throws \Exception When called + */ + public function offsetSet($offset, $value) + { + // Stringy is immutable, cannot directly set char + throw new Exception('Stringy object is immutable, cannot modify char'); + } + + /** + * Implements part of the ArrayAccess interface, but throws an exception + * when called. This maintains the immutability of Stringy objects. + * + * @param mixed $offset The index of the character + * @throws \Exception When called + */ + public function offsetUnset($offset) + { + // Don't allow directly modifying the string + throw new Exception('Stringy object is immutable, cannot unset char'); + } + + /** + * Pads the string to a given length with $padStr. If length is less than + * or equal to the length of the string, no padding takes places. The + * default string used for padding is a space, and the default type (one of + * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException + * if $padType isn't one of those 3 values. + * + * @param int $length Desired string length after padding + * @param string $padStr String used to pad, defaults to space + * @param string $padType One of 'left', 'right', 'both' + * @return static Object with a padded $str + * @throws /InvalidArgumentException If $padType isn't one of 'right', + * 'left' or 'both' + */ + public function pad($length, $padStr = ' ', $padType = 'right') + { + if (!in_array($padType, ['left', 'right', 'both'])) { + throw new InvalidArgumentException('Pad expects $padType ' . + "to be one of 'left', 'right' or 'both'"); + } + + switch ($padType) { + case 'left': + return $this->padLeft($length, $padStr); + case 'right': + return $this->padRight($length, $padStr); + default: + return $this->padBoth($length, $padStr); + } + } + + /** + * Returns a new string of a given length such that both sides of the + * string are padded. Alias for pad() with a $padType of 'both'. + * + * @param int $length Desired string length after padding + * @param string $padStr String used to pad, defaults to space + * @return static String with padding applied + */ + public function padBoth($length, $padStr = ' ') + { + $padding = $length - $this->length(); + + return $this->applyPadding(floor($padding / 2), ceil($padding / 2), + $padStr); + } + + /** + * Returns a new string of a given length such that the beginning of the + * string is padded. Alias for pad() with a $padType of 'left'. + * + * @param int $length Desired string length after padding + * @param string $padStr String used to pad, defaults to space + * @return static String with left padding + */ + public function padLeft($length, $padStr = ' ') + { + return $this->applyPadding($length - $this->length(), 0, $padStr); + } + + /** + * Returns a new string of a given length such that the end of the string + * is padded. Alias for pad() with a $padType of 'right'. + * + * @param int $length Desired string length after padding + * @param string $padStr String used to pad, defaults to space + * @return static String with right padding + */ + public function padRight($length, $padStr = ' ') + { + return $this->applyPadding(0, $length - $this->length(), $padStr); + } + + /** + * Returns a new string starting with $string. + * + * @param string $string The string to append + * @return static Object with appended $string + */ + public function prepend($string) + { + return static::create($string . $this->str, $this->encoding); + } + + /** + * Replaces all occurrences of $pattern in $str by $replacement. An alias + * for mb_ereg_replace(). Note that the 'i' option with multibyte patterns + * in mb_ereg_replace() requires PHP 5.6+ for correct results. This is due + * to a lack of support in the bundled version of Oniguruma in PHP < 5.6, + * and current versions of HHVM (3.8 and below). + * + * @param string $pattern The regular expression pattern + * @param string $replacement The string to replace with + * @param string $options Matching conditions to be used + * @return static Object with the resulting $str after the replacements + */ + public function regexReplace($pattern, $replacement, $options = 'msr') + { + $regexEncoding = $this->regexEncoding(); + $this->regexEncoding($this->encoding); + + $str = $this->eregReplace($pattern, $replacement, $this->str, $options); + $this->regexEncoding($regexEncoding); + + return static::create($str, $this->encoding); + } + + /** + * Returns a new string with the prefix $substring removed, if present. + * + * @param string $substring The prefix to remove + * @return static Object having a $str without the prefix $substring + */ + public function removeLeft($substring) + { + $stringy = static::create($this->str, $this->encoding); + + if ($stringy->startsWith($substring)) { + $substringLength = \mb_strlen($substring, $stringy->encoding); + return $stringy->substr($substringLength); + } + + return $stringy; + } + + /** + * Returns a new string with the suffix $substring removed, if present. + * + * @param string $substring The suffix to remove + * @return static Object having a $str without the suffix $substring + */ + public function removeRight($substring) + { + $stringy = static::create($this->str, $this->encoding); + + if ($stringy->endsWith($substring)) { + $substringLength = \mb_strlen($substring, $stringy->encoding); + return $stringy->substr(0, $stringy->length() - $substringLength); + } + + return $stringy; + } + + /** + * Returns a repeated string given a multiplier. An alias for str_repeat. + * + * @param int $multiplier The number of times to repeat the string + * @return static Object with a repeated str + */ + public function repeat($multiplier) + { + $repeated = str_repeat($this->str, $multiplier); + + return static::create($repeated, $this->encoding); + } + + /** + * Replaces all occurrences of $search in $str by $replacement. + * + * @param string $search The needle to search for + * @param string $replacement The string to replace with + * @return static Object with the resulting $str after the replacements + */ + public function replace($search, $replacement) + { + return $this->regexReplace(preg_quote($search), $replacement); + } + + /** + * Returns a reversed string. A multibyte version of strrev(). + * + * @return static Object with a reversed $str + */ + public function reverse() + { + $strLength = $this->length(); + $reversed = ''; + + // Loop from last index of string to first + for ($i = $strLength - 1; $i >= 0; $i--) { + $reversed .= \mb_substr($this->str, $i, 1, $this->encoding); + } + + return static::create($reversed, $this->encoding); + } + + /** + * Truncates the string to a given length, while ensuring that it does not + * split words. If $substring is provided, and truncating occurs, the + * string is further truncated so that the substring may be appended without + * exceeding the desired length. + * + * @param int $length Desired length of the truncated string + * @param string $substring The substring to append if it can fit + * @return static Object with the resulting $str after truncating + */ + public function safeTruncate($length, $substring = '') + { + $stringy = static::create($this->str, $this->encoding); + if ($length >= $stringy->length()) { + return $stringy; + } + + // Need to further trim the string so we can append the substring + $encoding = $stringy->encoding; + $substringLength = \mb_strlen($substring, $encoding); + $length = $length - $substringLength; + + $truncated = \mb_substr($stringy->str, 0, $length, $encoding); + + // If the last word was truncated + if (mb_strpos($stringy->str, ' ', $length - 1, $encoding) != $length) { + // Find pos of the last occurrence of a space, get up to that + $lastPos = \mb_strrpos($truncated, ' ', 0, $encoding); + if ($lastPos !== false) { + $truncated = \mb_substr($truncated, 0, $lastPos, $encoding); + } + } + + $stringy->str = $truncated . $substring; + + return $stringy; + } + + /* + * A multibyte str_shuffle() function. It returns a string with its + * characters in random order. + * + * @return static Object with a shuffled $str + */ + public function shuffle() + { + $indexes = range(0, $this->length() - 1); + shuffle($indexes); + + $shuffledStr = ''; + foreach ($indexes as $i) { + $shuffledStr .= \mb_substr($this->str, $i, 1, $this->encoding); + } + + return static::create($shuffledStr, $this->encoding); + } + + /** + * Converts the string into an URL slug. This includes replacing non-ASCII + * characters with their closest ASCII equivalents, removing remaining + * non-ASCII and non-alphanumeric characters, and replacing whitespace with + * $replacement. The replacement defaults to a single dash, and the string + * is also converted to lowercase. The language of the source string can + * also be supplied for language-specific transliteration. + * + * @param string $replacement The string used to replace whitespace + * @param string $language Language of the source string + * @return static Object whose $str has been converted to an URL slug + */ + public function slugify($replacement = '-', $language = 'en') + { + $stringy = $this->toAscii($language); + + $stringy->str = str_replace('@', $replacement, $stringy); + $quotedReplacement = preg_quote($replacement); + $pattern = "/[^a-zA-Z\d\s-_$quotedReplacement]/u"; + $stringy->str = preg_replace($pattern, '', $stringy); + + return $stringy->toLowerCase()->delimit($replacement) + ->removeLeft($replacement)->removeRight($replacement); + } + + /** + * Returns true if the string begins with $substring, false otherwise. By + * default, the comparison is case-sensitive, but can be made insensitive + * by setting $caseSensitive to false. + * + * @param string $substring The substring to look for + * @param bool $caseSensitive Whether or not to enforce + * case-sensitivity + * @return bool Whether or not $str starts with $substring + */ + public function startsWith($substring, $caseSensitive = true) + { + $substringLength = \mb_strlen($substring, $this->encoding); + $startOfStr = \mb_substr($this->str, 0, $substringLength, + $this->encoding); + + if (!$caseSensitive) { + $substring = \mb_strtolower($substring, $this->encoding); + $startOfStr = \mb_strtolower($startOfStr, $this->encoding); + } + + return (string) $substring === $startOfStr; + } + + /** + * Returns true if the string begins with any of $substrings, false + * otherwise. By default the comparison is case-sensitive, but can be made + * insensitive by setting $caseSensitive to false. + * + * @param string[] $substrings Substrings to look for + * @param bool $caseSensitive Whether or not to enforce + * case-sensitivity + * @return bool Whether or not $str starts with $substring + */ + public function startsWithAny($substrings, $caseSensitive = true) + { + if (empty($substrings)) { + return false; + } + + foreach ($substrings as $substring) { + if ($this->startsWith($substring, $caseSensitive)) { + return true; + } + } + + return false; + } + + /** + * Returns the substring beginning at $start, and up to, but not including + * the index specified by $end. If $end is omitted, the function extracts + * the remaining string. If $end is negative, it is computed from the end + * of the string. + * + * @param int $start Initial index from which to begin extraction + * @param int $end Optional index at which to end extraction + * @return static Object with its $str being the extracted substring + */ + public function slice($start, $end = null) + { + if ($end === null) { + $length = $this->length(); + } elseif ($end >= 0 && $end <= $start) { + return static::create('', $this->encoding); + } elseif ($end < 0) { + $length = $this->length() + $end - $start; + } else { + $length = $end - $start; + } + + return $this->substr($start, $length); + } + + /** + * Splits the string with the provided regular expression, returning an + * array of Stringy objects. An optional integer $limit will truncate the + * results. + * + * @param string $pattern The regex with which to split the string + * @param int $limit Optional maximum number of results to return + * @return static[] An array of Stringy objects + */ + public function split($pattern, $limit = null) + { + if ($limit === 0) { + return []; + } + + // mb_split errors when supplied an empty pattern in < PHP 5.4.13 + // and HHVM < 3.8 + if ($pattern === '') { + return [static::create($this->str, $this->encoding)]; + } + + $regexEncoding = $this->regexEncoding(); + $this->regexEncoding($this->encoding); + + // mb_split returns the remaining unsplit string in the last index when + // supplying a limit + $limit = ($limit > 0) ? $limit += 1 : -1; + + static $functionExists; + if ($functionExists === null) { + $functionExists = function_exists('\mb_split'); + } + + if ($functionExists) { + $array = \mb_split($pattern, $this->str, $limit); + } else if ($this->supportsEncoding()) { + $array = \preg_split("/$pattern/", $this->str, $limit); + } + + $this->regexEncoding($regexEncoding); + + if ($limit > 0 && count($array) === $limit) { + array_pop($array); + } + + for ($i = 0; $i < count($array); $i++) { + $array[$i] = static::create($array[$i], $this->encoding); + } + + return $array; + } + + /** + * Strip all whitespace characters. This includes tabs and newline + * characters, as well as multibyte whitespace such as the thin space + * and ideographic space. + * + * @return static Object with whitespace stripped + */ + public function stripWhitespace() + { + return $this->regexReplace('[[:space:]]+', ''); + } + + /** + * Returns the substring beginning at $start with the specified $length. + * It differs from the mb_substr() function in that providing a $length of + * null will return the rest of the string, rather than an empty string. + * + * @param int $start Position of the first character to use + * @param int $length Maximum number of characters used + * @return static Object with its $str being the substring + */ + public function substr($start, $length = null) + { + $length = $length === null ? $this->length() : $length; + $str = \mb_substr($this->str, $start, $length, $this->encoding); + + return static::create($str, $this->encoding); + } + + /** + * Surrounds $str with the given substring. + * + * @param string $substring The substring to add to both sides + * @return static Object whose $str had the substring both prepended and + * appended + */ + public function surround($substring) + { + $str = implode('', [$substring, $this->str, $substring]); + + return static::create($str, $this->encoding); + } + + /** + * Returns a case swapped version of the string. + * + * @return static Object whose $str has each character's case swapped + */ + public function swapCase() + { + $stringy = static::create($this->str, $this->encoding); + $encoding = $stringy->encoding; + + $stringy->str = preg_replace_callback( + '/[\S]/u', + function ($match) use ($encoding) { + if ($match[0] == \mb_strtoupper($match[0], $encoding)) { + return \mb_strtolower($match[0], $encoding); + } + + return \mb_strtoupper($match[0], $encoding); + }, + $stringy->str + ); + + return $stringy; + } + + /** + * Returns a string with smart quotes, ellipsis characters, and dashes from + * Windows-1252 (commonly used in Word documents) replaced by their ASCII + * equivalents. + * + * @return static Object whose $str has those characters removed + */ + public function tidy() + { + $str = preg_replace([ + '/\x{2026}/u', + '/[\x{201C}\x{201D}]/u', + '/[\x{2018}\x{2019}]/u', + '/[\x{2013}\x{2014}]/u', + ], [ + '...', + '"', + "'", + '-', + ], $this->str); + + return static::create($str, $this->encoding); + } + + /** + * Returns a trimmed string with the first letter of each word capitalized. + * Also accepts an array, $ignore, allowing you to list words not to be + * capitalized. + * + * @param array $ignore An array of words not to capitalize + * @return static Object with a titleized $str + */ + public function titleize($ignore = null) + { + $stringy = static::create($this->trim(), $this->encoding); + $encoding = $this->encoding; + + $stringy->str = preg_replace_callback( + '/([\S]+)/u', + function ($match) use ($encoding, $ignore) { + if ($ignore && in_array($match[0], $ignore)) { + return $match[0]; + } + + $stringy = new Stringy($match[0], $encoding); + + return (string) $stringy->toLowerCase()->upperCaseFirst(); + }, + $stringy->str + ); + + return $stringy; + } + + /** + * Returns an ASCII version of the string. A set of non-ASCII characters are + * replaced with their closest ASCII counterparts, and the rest are removed + * by default. The language or locale of the source string can be supplied + * for language-specific transliteration in any of the following formats: + * en, en_GB, or en-GB. For example, passing "de" results in "äöü" mapping + * to "aeoeue" rather than "aou" as in other languages. + * + * @param string $language Language of the source string + * @param bool $removeUnsupported Whether or not to remove the + * unsupported characters + * @return static Object whose $str contains only ASCII characters + */ + public function toAscii($language = 'en', $removeUnsupported = true) + { + $str = $this->str; + + $langSpecific = $this->langSpecificCharsArray($language); + if (!empty($langSpecific)) { + $str = str_replace($langSpecific[0], $langSpecific[1], $str); + } + + foreach ($this->charsArray() as $key => $value) { + $str = str_replace($value, $key, $str); + } + + if ($removeUnsupported) { + $str = preg_replace('/[^\x20-\x7E]/u', '', $str); + } + + return static::create($str, $this->encoding); + } + + /** + * Returns a boolean representation of the given logical string value. + * For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0', + * 'off', and 'no' will return false. In all instances, case is ignored. + * For other numeric strings, their sign will determine the return value. + * In addition, blank strings consisting of only whitespace will return + * false. For all other strings, the return value is a result of a + * boolean cast. + * + * @return bool A boolean value for the string + */ + public function toBoolean() + { + $key = $this->toLowerCase()->str; + $map = [ + 'true' => true, + '1' => true, + 'on' => true, + 'yes' => true, + 'false' => false, + '0' => false, + 'off' => false, + 'no' => false + ]; + + if (array_key_exists($key, $map)) { + return $map[$key]; + } elseif (is_numeric($this->str)) { + return (intval($this->str) > 0); + } + + return (bool) $this->regexReplace('[[:space:]]', '')->str; + } + + /** + * Converts all characters in the string to lowercase. An alias for PHP's + * mb_strtolower(). + * + * @return static Object with all characters of $str being lowercase + */ + public function toLowerCase() + { + $str = \mb_strtolower($this->str, $this->encoding); + + return static::create($str, $this->encoding); + } + + /** + * Converts each tab in the string to some number of spaces, as defined by + * $tabLength. By default, each tab is converted to 4 consecutive spaces. + * + * @param int $tabLength Number of spaces to replace each tab with + * @return static Object whose $str has had tabs switched to spaces + */ + public function toSpaces($tabLength = 4) + { + $spaces = str_repeat(' ', $tabLength); + $str = str_replace("\t", $spaces, $this->str); + + return static::create($str, $this->encoding); + } + + /** + * Converts each occurrence of some consecutive number of spaces, as + * defined by $tabLength, to a tab. By default, each 4 consecutive spaces + * are converted to a tab. + * + * @param int $tabLength Number of spaces to replace with a tab + * @return static Object whose $str has had spaces switched to tabs + */ + public function toTabs($tabLength = 4) + { + $spaces = str_repeat(' ', $tabLength); + $str = str_replace($spaces, "\t", $this->str); + + return static::create($str, $this->encoding); + } + + /** + * Converts the first character of each word in the string to uppercase. + * + * @return static Object with all characters of $str being title-cased + */ + public function toTitleCase() + { + $str = \mb_convert_case($this->str, \MB_CASE_TITLE, $this->encoding); + + return static::create($str, $this->encoding); + } + + /** + * Converts all characters in the string to uppercase. An alias for PHP's + * mb_strtoupper(). + * + * @return static Object with all characters of $str being uppercase + */ + public function toUpperCase() + { + $str = \mb_strtoupper($this->str, $this->encoding); + + return static::create($str, $this->encoding); + } + + /** + * Returns a string with whitespace removed from the start and end of the + * string. Supports the removal of unicode whitespace. Accepts an optional + * string of characters to strip instead of the defaults. + * + * @param string $chars Optional string of characters to strip + * @return static Object with a trimmed $str + */ + public function trim($chars = null) + { + $chars = ($chars) ? preg_quote($chars) : '[:space:]'; + + return $this->regexReplace("^[$chars]+|[$chars]+\$", ''); + } + + /** + * Returns a string with whitespace removed from the start of the string. + * Supports the removal of unicode whitespace. Accepts an optional + * string of characters to strip instead of the defaults. + * + * @param string $chars Optional string of characters to strip + * @return static Object with a trimmed $str + */ + public function trimLeft($chars = null) + { + $chars = ($chars) ? preg_quote($chars) : '[:space:]'; + + return $this->regexReplace("^[$chars]+", ''); + } + + /** + * Returns a string with whitespace removed from the end of the string. + * Supports the removal of unicode whitespace. Accepts an optional + * string of characters to strip instead of the defaults. + * + * @param string $chars Optional string of characters to strip + * @return static Object with a trimmed $str + */ + public function trimRight($chars = null) + { + $chars = ($chars) ? preg_quote($chars) : '[:space:]'; + + return $this->regexReplace("[$chars]+\$", ''); + } + + /** + * Truncates the string to a given length. If $substring is provided, and + * truncating occurs, the string is further truncated so that the substring + * may be appended without exceeding the desired length. + * + * @param int $length Desired length of the truncated string + * @param string $substring The substring to append if it can fit + * @return static Object with the resulting $str after truncating + */ + public function truncate($length, $substring = '') + { + $stringy = static::create($this->str, $this->encoding); + if ($length >= $stringy->length()) { + return $stringy; + } + + // Need to further trim the string so we can append the substring + $substringLength = \mb_strlen($substring, $stringy->encoding); + $length = $length - $substringLength; + + $truncated = \mb_substr($stringy->str, 0, $length, $stringy->encoding); + $stringy->str = $truncated . $substring; + + return $stringy; + } + + /** + * Returns a lowercase and trimmed string separated by underscores. + * Underscores are inserted before uppercase characters (with the exception + * of the first character of the string), and in place of spaces as well as + * dashes. + * + * @return static Object with an underscored $str + */ + public function underscored() + { + return $this->delimit('_'); + } + + /** + * Returns an UpperCamelCase version of the supplied string. It trims + * surrounding spaces, capitalizes letters following digits, spaces, dashes + * and underscores, and removes spaces, dashes, underscores. + * + * @return static Object with $str in UpperCamelCase + */ + public function upperCamelize() + { + return $this->camelize()->upperCaseFirst(); + } + + /** + * Converts the first character of the supplied string to upper case. + * + * @return static Object with the first character of $str being upper case + */ + public function upperCaseFirst() + { + $first = \mb_substr($this->str, 0, 1, $this->encoding); + $rest = \mb_substr($this->str, 1, $this->length() - 1, + $this->encoding); + + $str = \mb_strtoupper($first, $this->encoding) . $rest; + + return static::create($str, $this->encoding); + } + + /** + * Returns the replacements for the toAscii() method. + * + * @return array An array of replacements. + */ + protected function charsArray() + { + static $charsArray; + if (isset($charsArray)) return $charsArray; + + return $charsArray = [ + '0' => ['°', '₀', '۰', '0'], + '1' => ['¹', '₁', '۱', '1'], + '2' => ['²', '₂', '۲', '2'], + '3' => ['³', '₃', '۳', '3'], + '4' => ['⁴', '₄', '۴', '٤', '4'], + '5' => ['⁵', '₅', '۵', '٥', '5'], + '6' => ['⁶', '₆', '۶', '٦', '6'], + '7' => ['⁷', '₇', '۷', '7'], + '8' => ['⁸', '₈', '۸', '8'], + '9' => ['⁹', '₉', '۹', '9'], + 'a' => ['à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', + 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ā', 'ą', 'å', + 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', 'ἇ', + 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', 'ά', + 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ', 'အ', + 'ာ', 'ါ', 'ǻ', 'ǎ', 'ª', 'ა', 'अ', 'ا', 'a', 'ä'], + 'b' => ['б', 'β', 'ب', 'ဗ', 'ბ', 'b'], + 'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ', 'c'], + 'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', + 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ', 'd'], + 'e' => ['é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', + 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', + 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', + 'є', 'ə', 'ဧ', 'ေ', 'ဲ', 'ე', 'ए', 'إ', 'ئ', 'e'], + 'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ', 'f'], + 'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ', + 'g'], + 'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ', 'h'], + 'i' => ['í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', + 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', + 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', + 'ῗ', 'і', 'ї', 'и', 'ဣ', 'ိ', 'ီ', 'ည်', 'ǐ', 'ი', + 'इ', 'ی', 'i'], + 'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج', 'j'], + 'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', + 'ک', 'k'], + 'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ', + 'l'], + 'm' => ['м', 'μ', 'م', 'မ', 'მ', 'm'], + 'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', + 'ნ', 'n'], + 'o' => ['ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', + 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', + 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', + 'о', 'و', 'θ', 'ို', 'ǒ', 'ǿ', 'º', 'ო', 'ओ', 'o', + 'ö'], + 'p' => ['п', 'π', 'ပ', 'პ', 'پ', 'p'], + 'q' => ['ყ', 'q'], + 'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ', 'r'], + 's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', + 'ſ', 'ს', 's'], + 't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', + 'თ', 'ტ', 't'], + 'u' => ['ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', + 'ự', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у', 'ဉ', + 'ု', 'ူ', 'ǔ', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'უ', 'उ', 'u', + 'ў', 'ü'], + 'v' => ['в', 'ვ', 'ϐ', 'v'], + 'w' => ['ŵ', 'ω', 'ώ', 'ဝ', 'ွ', 'w'], + 'x' => ['χ', 'ξ', 'x'], + 'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', + 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ', 'y'], + 'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ', 'z'], + 'aa' => ['ع', 'आ', 'آ'], + 'ae' => ['æ', 'ǽ'], + 'ai' => ['ऐ'], + 'ch' => ['ч', 'ჩ', 'ჭ', 'چ'], + 'dj' => ['ђ', 'đ'], + 'dz' => ['џ', 'ძ'], + 'ei' => ['ऍ'], + 'gh' => ['غ', 'ღ'], + 'ii' => ['ई'], + 'ij' => ['ij'], + 'kh' => ['х', 'خ', 'ხ'], + 'lj' => ['љ'], + 'nj' => ['њ'], + 'oe' => ['œ', 'ؤ'], + 'oi' => ['ऑ'], + 'oii' => ['ऒ'], + 'ps' => ['ψ'], + 'sh' => ['ш', 'შ', 'ش'], + 'shch' => ['щ'], + 'ss' => ['ß'], + 'sx' => ['ŝ'], + 'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'], + 'ts' => ['ц', 'ც', 'წ'], + 'uu' => ['ऊ'], + 'ya' => ['я'], + 'yu' => ['ю'], + 'zh' => ['ж', 'ჟ', 'ژ'], + '(c)' => ['©'], + 'A' => ['Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', + 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Å', 'Ā', 'Ą', + 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', 'Ἇ', + 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', 'Ᾱ', + 'Ὰ', 'Ά', 'ᾼ', 'А', 'Ǻ', 'Ǎ', 'A', 'Ä'], + 'B' => ['Б', 'Β', 'ब', 'B'], + 'C' => ['Ç','Ć', 'Č', 'Ĉ', 'Ċ', 'C'], + 'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ', + 'D'], + 'E' => ['É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', + 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', + 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', + 'Є', 'Ə', 'E'], + 'F' => ['Ф', 'Φ', 'F'], + 'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ', 'G'], + 'H' => ['Η', 'Ή', 'Ħ', 'H'], + 'I' => ['Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', + 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', + 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї', 'Ǐ', 'ϒ', + 'I'], + 'J' => ['J'], + 'K' => ['К', 'Κ', 'K'], + 'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल', 'L'], + 'M' => ['М', 'Μ', 'M'], + 'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν', 'N'], + 'O' => ['Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', + 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ø', 'Ō', 'Ő', + 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', 'Ὸ', + 'Ό', 'О', 'Θ', 'Ө', 'Ǒ', 'Ǿ', 'O', 'Ö'], + 'P' => ['П', 'Π', 'P'], + 'Q' => ['Q'], + 'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ', 'R'], + 'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ', 'S'], + 'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ', 'T'], + 'U' => ['Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', + 'Ự', 'Û', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У', 'Ǔ', 'Ǖ', + 'Ǘ', 'Ǚ', 'Ǜ', 'U', 'Ў', 'Ü'], + 'V' => ['В', 'V'], + 'W' => ['Ω', 'Ώ', 'Ŵ', 'W'], + 'X' => ['Χ', 'Ξ', 'X'], + 'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', + 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ', 'Y'], + 'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ', 'Z'], + 'AE' => ['Æ', 'Ǽ'], + 'Ch' => ['Ч'], + 'Dj' => ['Ђ'], + 'Dz' => ['Џ'], + 'Gx' => ['Ĝ'], + 'Hx' => ['Ĥ'], + 'Ij' => ['IJ'], + 'Jx' => ['Ĵ'], + 'Kh' => ['Х'], + 'Lj' => ['Љ'], + 'Nj' => ['Њ'], + 'Oe' => ['Œ'], + 'Ps' => ['Ψ'], + 'Sh' => ['Ш'], + 'Shch' => ['Щ'], + 'Ss' => ['ẞ'], + 'Th' => ['Þ'], + 'Ts' => ['Ц'], + 'Ya' => ['Я'], + 'Yu' => ['Ю'], + 'Zh' => ['Ж'], + ' ' => ["\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", + "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", + "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", + "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", + "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80", + "\xEF\xBE\xA0"], + ]; + } + + /** + * Returns language-specific replacements for the toAscii() method. + * For example, German will map 'ä' to 'ae', while other languages + * will simply return 'a'. + * + * @param string $language Language of the source string + * @return array An array of replacements. + */ + protected static function langSpecificCharsArray($language = 'en') + { + $split = preg_split('/[-_]/', $language); + $language = strtolower($split[0]); + + static $charsArray = []; + if (isset($charsArray[$language])) { + return $charsArray[$language]; + } + + $languageSpecific = [ + 'de' => [ + ['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü' ], + ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], + ], + 'bg' => [ + ['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'], + ['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'] + ] + ]; + + if (isset($languageSpecific[$language])) { + $charsArray[$language] = $languageSpecific[$language]; + } else { + $charsArray[$language] = []; + } + + return $charsArray[$language]; + } + + /** + * Adds the specified amount of left and right padding to the given string. + * The default character used is a space. + * + * @param int $left Length of left padding + * @param int $right Length of right padding + * @param string $padStr String used to pad + * @return static String with padding applied + */ + protected function applyPadding($left = 0, $right = 0, $padStr = ' ') + { + $stringy = static::create($this->str, $this->encoding); + $length = \mb_strlen($padStr, $stringy->encoding); + + $strLength = $stringy->length(); + $paddedLength = $strLength + $left + $right; + + if (!$length || $paddedLength <= $strLength) { + return $stringy; + } + + $leftPadding = \mb_substr(str_repeat($padStr, ceil($left / $length)), 0, + $left, $stringy->encoding); + $rightPadding = \mb_substr(str_repeat($padStr, ceil($right / $length)), + 0, $right, $stringy->encoding); + + $stringy->str = $leftPadding . $stringy->str . $rightPadding; + + return $stringy; + } + + /** + * Returns true if $str matches the supplied pattern, false otherwise. + * + * @param string $pattern Regex pattern to match against + * @return bool Whether or not $str matches the pattern + */ + protected function matchesPattern($pattern) + { + $regexEncoding = $this->regexEncoding(); + $this->regexEncoding($this->encoding); + + $match = \mb_ereg_match($pattern, $this->str); + $this->regexEncoding($regexEncoding); + + return $match; + } + + /** + * Alias for mb_ereg_replace with a fallback to preg_replace if the + * mbstring module is not installed. + */ + protected function eregReplace($pattern, $replacement, $string, $option = 'msr') + { + static $functionExists; + if ($functionExists === null) { + $functionExists = function_exists('\mb_split'); + } + + if ($functionExists) { + return \mb_ereg_replace($pattern, $replacement, $string, $option); + } else if ($this->supportsEncoding()) { + $option = str_replace('r', '', $option); + return \preg_replace("/$pattern/u$option", $replacement, $string); + } + } + + /** + * Alias for mb_regex_encoding which default to a noop if the mbstring + * module is not installed. + */ + protected function regexEncoding() + { + static $functionExists; + + if ($functionExists === null) { + $functionExists = function_exists('\mb_regex_encoding'); + } + + if ($functionExists) { + $args = func_get_args(); + return call_user_func_array('\mb_regex_encoding', $args); + } + } + + protected function supportsEncoding() + { + $supported = ['UTF-8' => true, 'ASCII' => true]; + + if (isset($supported[$this->encoding])) { + return true; + } else { + throw new \RuntimeException('Stringy method requires the ' . + 'mbstring module for encodings other than ASCII and UTF-8. ' . + 'Encoding used: ' . $this->encoding); + } + } +} 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..67738da --- /dev/null +++ b/vendor/guzzlehttp/guzzle/CHANGELOG.md @@ -0,0 +1,1318 @@ +# Change Log + +## 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 retires due unit mismatch. [#2132](https://github.com/guzzle/guzzle/pull/2132) +* 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..50a177b --- /dev/null +++ b/vendor/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2018 Michael Dowling, https://github.com/mtdowling + +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..5fdb6c5 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/README.md @@ -0,0 +1,90 @@ +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/travis/guzzle/guzzle.svg?style=flat-square)](https://travis-ci.org/guzzle/guzzle) +[![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 | +| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 | +| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 | +| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 | + +[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 +[guzzle-3-docs]: http://guzzle3.readthedocs.org +[guzzle-5-docs]: http://guzzle.readthedocs.org/en/5.3/ +[guzzle-6-docs]: http://guzzle.readthedocs.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..bbf0ff2 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/composer.json @@ -0,0 +1,59 @@ +{ + "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": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.5", + "ext-json": "*", + "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", + "ext-intl": "Required for Internationalized Domain Name (IDN) support" + }, + "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..f2bf204 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Client.php @@ -0,0 +1,511 @@ + '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. "handler" is a + * constructor only option that cannot be overridden in per/request + * options. 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 = _idn_uri_convert($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_to_ascii() is a part of ext-intl and might be not available + $defaults['idn_conversion'] = function_exists('idn_to_ascii') + // Old ICU versions don't have this constant, so we are basically stuck (see https://github.com/guzzle/guzzle/pull/2424 + // and https://github.com/guzzle/guzzle/issues/2448 for details) + && ( + defined('INTL_IDNA_VARIANT_UTS46') + || + PHP_VERSION_ID < 70200 + ); + + // 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..0c8d42a --- /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)); + } + $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..3d776a7 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,403 @@ + 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) + { + // Remove the leading '.' as per spec in RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = ltrim($this->getDomain(), '.'); + + // Domain not set or exact match. + if (!$cookieDomain || !strcasecmp($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..b73e5c7 --- /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 = \GuzzleHttp\_current_time(); + 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] = \GuzzleHttp\_current_time() + ($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 = \GuzzleHttp\_current_time(); + $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, + \GuzzleHttp\_current_time() - $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..2f30126 --- /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 = _idn_uri_convert($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/functions.php b/vendor/guzzlehttp/guzzle/src/functions.php new file mode 100644 index 0000000..1924d6d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/functions.php @@ -0,0 +1,392 @@ +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. + * + * @throws \RuntimeException if no viable Handler is available. + * @return callable Returns the best handler for the given system. + */ +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; +} + +/** + * Wrapper for the hrtime() or microtime() functions + * (depending on the PHP version, one of the two is used) + * + * @return float|mixed UNIX timestamp + * @internal + */ +function _current_time() +{ + return function_exists('hrtime') ? hrtime(true) / 1e9 : microtime(true); +} + + +/** + * @param int $options + * + * @return UriInterface + * + * @internal + */ +function _idn_uri_convert(UriInterface $uri, $options = 0) +{ + if ($uri->getHost()) { + $idnaVariant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : 0; + $asciiHost = idn_to_ascii($uri->getHost(), $options, $idnaVariant, $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; +} 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 @@ + + +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..7b607e2 --- /dev/null +++ b/vendor/guzzlehttp/promises/README.md @@ -0,0 +1,504 @@ +# 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()`. + + +# 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; +use GuzzleHttp\Promise\RejectedPromise; + +$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\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" +``` diff --git a/vendor/guzzlehttp/promises/composer.json b/vendor/guzzlehttp/promises/composer.json new file mode 100644 index 0000000..ec41a61 --- /dev/null +++ b/vendor/guzzlehttp/promises/composer.json @@ -0,0 +1,34 @@ +{ + "name": "guzzlehttp/promises", + "description": "Guzzle promises library", + "keywords": ["promise"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-ci": "vendor/bin/phpunit --coverage-text" + }, + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + } +} diff --git a/vendor/guzzlehttp/promises/src/AggregateException.php b/vendor/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 0000000..6a5690c --- /dev/null +++ b/vendor/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,16 @@ +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(); + } + }); + $this->nextCoroutine($this->generator->current()); + } + + 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 = promise_for($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(exception_for($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/EachPromise.php b/vendor/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 0000000..d0ddf60 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,229 @@ +iterable = iter_for($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']; + } + } + + public function promise() + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Throwable $e) { + $this->aggregate->reject($e); + } catch (\Exception $e) { + $this->aggregate->reject($e); + } + + return $this->aggregate; + } + + private function createPromise() + { + $this->mutex = false; + $this->aggregate = new Promise(function () { + reset($this->pending); + if (empty($this->pending) && !$this->iterable->valid()) { + $this->aggregate->resolve(null); + return; + } + + // 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 ($this->aggregate->getState() !== PromiseInterface::PENDING) { + return; + } + } + }); + + // Clear the references when the promise is resolved. + $clearFn = function () { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + }; + + $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 = promise_for($this->iterable->current()); + $idx = $this->iterable->key(); + + $this->pending[$idx] = $promise->then( + function ($value) use ($idx) { + if ($this->onFulfilled) { + call_user_func( + $this->onFulfilled, $value, $idx, $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx) { + if ($this->onRejected) { + call_user_func( + $this->onRejected, $reason, $idx, $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 ($this->aggregate->getState() !== PromiseInterface::PENDING) { + 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..dbbeeb9 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,82 @@ +value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + + $queue = queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use ($p, $value, $onFulfilled) { + if ($p->getState() === self::PENDING) { + 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/Promise.php b/vendor/guzzlehttp/promises/src/Promise.php new file mode 100644 index 0000000..844ada0 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,280 @@ +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) { + return $onFulfilled + ? promise_for($this->result)->then($onFulfilled) + : promise_for($this->result); + } + + // It's either cancelled or rejected, so return a rejected promise + // and immediately invoke any callbacks. + $rejection = rejection_for($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(); + + $inner = $this->result instanceof PromiseInterface + ? $this->result->wait($unwrap) + : $this->result; + + if ($unwrap) { + if ($this->result instanceof PromiseInterface + || $this->state === self::FULFILLED + ) { + return $inner; + } else { + // It's rejected so "unwrap" and throw an exception. + throw exception_for($inner); + } + } + } + + 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. + 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 (!method_exists($value, 'then')) { + $id = $state === self::FULFILLED ? 1 : 2; + // It's a success, so resolve the handlers in the queue. + queue()->add(static function () use ($id, $value, $handlers) { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise + && $value->getState() === self::PENDING + ) { + // 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). + * + * @return array Returns the next group to resolve. + */ + 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 ($promise->getState() !== self::PENDING) { + return; + } + + try { + if (isset($handler[$index])) { + $promise->resolve($handler[$index]($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 not 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.'); + } + + queue()->run(); + + 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) { + while (true) { + $result->waitIfPending(); + + if ($result->result instanceof Promise) { + $result = $result->result; + } else { + if ($result->result instanceof PromiseInterface) { + $result->result->wait(false); + } + break; + } + } + } + } +} diff --git a/vendor/guzzlehttp/promises/src/PromiseInterface.php b/vendor/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 0000000..8f5f4b9 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,93 @@ +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 = queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use ($p, $reason, $onRejected) { + if ($p->getState() === self::PENDING) { + 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 exception_for($this->reason); + } + } + + 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..07c1136 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,47 @@ +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..6e8a2a0 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,66 @@ +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() + { + /** @var callable $task */ + while ($task = array_shift($this->queue)) { + $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..ac8306e --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php @@ -0,0 +1,25 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + */ +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 + */ +function task(callable $task) +{ + $queue = queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise) { + try { + $promise->resolve($task()); + } catch (\Throwable $e) { + $promise->reject($e); + } catch (\Exception $e) { + $promise->reject($e); + } + }); + + return $promise; +} + +/** + * Creates a promise for a value if the value is not a promise. + * + * @param mixed $value Promise or value. + * + * @return PromiseInterface + */ +function promise_for($value) +{ + if ($value instanceof PromiseInterface) { + return $value; + } + + // Return a Guzzle promise that shadows the given promise. + if (method_exists($value, 'then')) { + $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null; + $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null; + $promise = new Promise($wfn, $cfn); + $value->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 + */ +function rejection_for($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 + */ +function exception_for($reason) +{ + return $reason instanceof \Exception || $reason instanceof \Throwable + ? $reason + : new RejectionException($reason); +} + +/** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + */ +function iter_for($value) +{ + if ($value instanceof \Iterator) { + return $value; + } elseif (is_array($value)) { + return new \ArrayIterator($value); + } else { + return new \ArrayIterator([$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 + */ +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. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + * @see GuzzleHttp\Promise\inspect for the inspection state array format. + */ +function inspect_all($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 mixed $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + */ +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. + * + * @return PromiseInterface + */ +function all($promises) +{ + $results = []; + return each( + $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; + }); +} + +/** + * 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 prommise is rejected with a {@see GuzzleHttp\Promise\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 + */ +function some($count, $promises) +{ + $results = []; + $rejections = []; + + return each( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count) { + if ($p->getState() !== PromiseInterface::PENDING) { + 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 + */ +function any($promises) +{ + return 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. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * @see GuzzleHttp\Promise\inspect for the inspection state array format. + */ +function settle($promises) +{ + $results = []; + + return each( + $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; + }); +} + +/** + * 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 promise 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 promise if needed. + * + * @param mixed $iterable Iterator or array to iterate over. + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + */ +function each( + $iterable, + callable $onFulfilled = null, + callable $onRejected = null +) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected + ]))->promise(); +} + +/** + * 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 + */ +function each_limit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null +) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected, + 'concurrency' => $concurrency + ]))->promise(); +} + +/** + * 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 + */ +function each_limit_all( + $iterable, + $concurrency, + callable $onFulfilled = null +) { + return each_limit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate) { + $aggregate->reject($reason); + } + ); +} + +/** + * Returns true if a promise is fulfilled. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_fulfilled(PromiseInterface $promise) +{ + return $promise->getState() === PromiseInterface::FULFILLED; +} + +/** + * Returns true if a promise is rejected. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_rejected(PromiseInterface $promise) +{ + return $promise->getState() === PromiseInterface::REJECTED; +} + +/** + * Returns true if a promise is fulfilled or rejected. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_settled(PromiseInterface $promise) +{ + return $promise->getState() !== PromiseInterface::PENDING; +} + +/** + * @see Coroutine + * + * @param callable $generatorFn + * + * @return PromiseInterface + */ +function coroutine(callable $generatorFn) +{ + return new Coroutine($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 @@ +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` + + + +[Unreleased]: https://github.com/guzzle/psr7/compare/1.6.0...HEAD +[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..581d95f --- /dev/null +++ b/vendor/guzzlehttp/psr7/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling + +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..c60a6a3 --- /dev/null +++ b/vendor/guzzlehttp/psr7/README.md @@ -0,0 +1,745 @@ +# 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\stream_for('abc, '); +$b = Psr7\stream_for('123.'); +$composed = new Psr7\AppendStream([$a, $b]); + +$composed->addStream(Psr7\stream_for(' 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\stream_for(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\stream_for(); + +// 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\stream_for('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\stream_for(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\stream_for('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\stream_for('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\stream_for('hello!'); +$resource = StreamWrapper::getResource($stream); +echo fread($resource, 6); // outputs hello! +``` + + +# Function API + +There are various functions available under the `GuzzleHttp\Psr7` namespace. + + +## `function str` + +`function str(MessageInterface $message)` + +Returns the string representation of an HTTP message. + +```php +$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); +echo GuzzleHttp\Psr7\str($request); +``` + + +## `function uri_for` + +`function uri_for($uri)` + +This function accepts a string or `Psr\Http\Message\UriInterface` and returns a +UriInterface for the given value. If the value is already a `UriInterface`, it +is returned as-is. + +```php +$uri = GuzzleHttp\Psr7\uri_for('http://example.com'); +assert($uri === GuzzleHttp\Psr7\uri_for($uri)); +``` + + +## `function stream_for` + +`function stream_for($resource = '', array $options = [])` + +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\stream_for('foo'); +$stream = GuzzleHttp\Psr7\stream_for(fopen('/path/to/file', 'r')); + +$generator = function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +} + +$stream = GuzzleHttp\Psr7\stream_for($generator(100)); +``` + + +## `function parse_header` + +`function parse_header($header)` + +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. + + +## `function normalize_header` + +`function normalize_header($header)` + +Converts an array of header values that may contain comma separated headers +into an array of headers with no comma separated values. + + +## `function modify_request` + +`function modify_request(RequestInterface $request, array $changes)` + +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. + + +## `function rewind_body` + +`function rewind_body(MessageInterface $message)` + +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`. + + +## `function try_fopen` + +`function try_fopen($filename, $mode)` + +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. + + +## `function copy_to_string` + +`function copy_to_string(StreamInterface $stream, $maxLen = -1)` + +Copy the contents of a stream into a string until the given number of bytes +have been read. + + +## `function copy_to_stream` + +`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)` + +Copy the contents of a stream into another stream until the given number of +bytes have been read. + + +## `function hash` + +`function hash(StreamInterface $stream, $algo, $rawOutput = false)` + +Calculate a hash of a Stream. This method reads the entire stream to calculate +a rolling hash (based on PHP's hash_init functions). + + +## `function readline` + +`function readline(StreamInterface $stream, $maxLength = null)` + +Read a line from the stream up to the maximum allowed buffer length. + + +## `function parse_request` + +`function parse_request($message)` + +Parses a request message string into a request object. + + +## `function parse_response` + +`function parse_response($message)` + +Parses a response message string into a response object. + + +## `function parse_query` + +`function parse_query($str, $urlEncoding = true)` + +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']`). + + +## `function build_query` + +`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)` + +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). + + +## `function mimetype_from_filename` + +`function mimetype_from_filename($filename)` + +Determines the mimetype of a file by looking at its extension. + + +## `function mimetype_from_extension` + +`function mimetype_from_extension($extension)` + +Maps a file extensions to a mimetype. + + +# 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. diff --git a/vendor/guzzlehttp/psr7/composer.json b/vendor/guzzlehttp/psr7/composer.json new file mode 100644 index 0000000..168a055 --- /dev/null +++ b/vendor/guzzlehttp/psr7/composer.json @@ -0,0 +1,49 @@ +{ + "name": "guzzlehttp/psr7", + "type": "library", + "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": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "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.8", + "ext-zlib": "*" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "suggest": { + "zendframework/zend-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.6-dev" + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/AppendStream.php b/vendor/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 0000000..472a0d6 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,241 @@ +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 copy_to_string($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 = []; + } + + 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..af4d4c2 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,137 @@ +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(); + } + + 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..ed68f08 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,138 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(fopen('php://temp', 'r+')); + } + + public function getSize() + { + return max($this->stream->getSize(), $this->remoteStream->getSize()); + } + + 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']); + copy_to_stream($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..8935c80 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,42 @@ +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..73daea6 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/FnStream.php @@ -0,0 +1,158 @@ +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/InflateStream.php b/vendor/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 0000000..5e4f602 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,52 @@ +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..02cec3a --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,39 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + * + * @return StreamInterface + */ + protected function createStream() + { + return stream_for(try_fopen($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..e4f239e --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,155 @@ +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/MessageTrait.php b/vendor/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 0000000..a7966d1 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,213 @@ + 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 */ + 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 = stream_for(''); + } + + 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; + } + } + } + + private function normalizeHeaderValue($value) + { + if (!is_array($value)) { + return $this->trimHeaderValues([$value]); + } + + if (count($value) === 0) { + throw new \InvalidArgumentException('Header value can not be an empty array.'); + } + + return $this->trimHeaderValues($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 string[] $values Header values + * + * @return string[] Trimmed header values + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function trimHeaderValues(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) + )); + } + + return trim((string) $value, " \t"); + }, $values); + } + + 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.'); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/MultipartStream.php b/vendor/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 0000000..c0fd584 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,153 @@ +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(stream_for("--{$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'] = stream_for($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(stream_for($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(stream_for("\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_from_filename($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..2332218 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,22 @@ +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 copy_to_string($this); + } catch (\Exception $e) { + return ''; + } + } + + public function close() + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = false; + $this->source = 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/Request.php b/vendor/guzzlehttp/psr7/src/Request.php new file mode 100644 index 0000000..59f337d --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,151 @@ +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 = stream_for($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..e7e04d8 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,154 @@ + '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|null|resource|StreamInterface $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 = stream_for($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 = $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..505e474 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Rfc7230.php @@ -0,0 +1,18 @@ +@,;:\\\"/[\]?={}\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..1a09a6c --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,376 @@ +serverParams = $serverParams; + + parent::__construct($method, $uri, $headers, $body, $version); + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files A array which respect $_FILES structure + * @throws InvalidArgumentException for unrecognized values + * @return array + */ + 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..d9e7409 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,267 @@ +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 { + $this->seek(0); + return (string) stream_get_contents($this->stream); + } 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..daec6f5 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,149 @@ +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 copy_to_string($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..0f3a285 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,161 @@ +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..e62bd5c --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,316 @@ +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 boolean + */ + private function isStringOrNull($param) + { + return in_array(gettype($param), ['string', 'NULL']); + } + + /** + * @param mixed $param + * @return boolean + */ + 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 boolean + */ + private function isOk() + { + return $this->error === UPLOAD_ERR_OK; + } + + /** + * @return boolean + */ + 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 { + copy_to_stream( + $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..825a25e --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,760 @@ + 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 = parse_url($uri); + if ($parts === false) { + throw new \InvalidArgumentException("Unable to parse URI: $uri"); + } + $this->applyParts($parts); + } + } + + 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 strtolower($scheme); + } + + /** + * @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 strtolower($host); + } + + /** + * @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..384c29e --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,216 @@ +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..c1cb8a2 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,219 @@ +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/functions.php b/vendor/guzzlehttp/psr7/src/functions.php new file mode 100644 index 0000000..8e6dafe --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/functions.php @@ -0,0 +1,899 @@ +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) { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + + return "{$msg}\r\n\r\n" . $message->getBody(); +} + +/** + * Returns a UriInterface for the given value. + * + * This function accepts a string or {@see Psr\Http\Message\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 + */ +function uri_for($uri) +{ + if ($uri instanceof UriInterface) { + return $uri; + } elseif (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); +} + +/** + * 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. + * + * @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data + * @param array $options Additional options + * + * @return StreamInterface + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ +function stream_for($resource = '', array $options = []) +{ + if (is_scalar($resource)) { + $stream = fopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, $resource); + fseek($stream, 0); + } + return new Stream($stream, $options); + } + + switch (gettype($resource)) { + case 'resource': + 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 stream_for((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(fopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); +} + +/** + * 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. + * + * @param string|array $header Header to parse into components. + * + * @return array Returns the parsed header values. + */ +function parse_header($header) +{ + static $trimmed = "\"' \n\t\r"; + $params = $matches = []; + + foreach (normalize_header($header) as $val) { + $part = []; + foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { + if (preg_match_all('/<[^>]+>|[^=]+/', $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. + */ +function normalize_header($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; +} + +/** + * Clone and modify a request with the given changes. + * + * 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 + */ +function modify_request(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 = _caseless_remove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = _caseless_remove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + + if ($request instanceof ServerRequestInterface) { + return (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()); + } + + 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() + ); +} + +/** + * 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 + */ +function rewind_body(MessageInterface $message) +{ + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } +} + +/** + * 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 + */ +function try_fopen($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] + )); + }); + + $handle = fopen($filename, $mode); + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; +} + +/** + * 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. + */ +function copy_to_string(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; +} + +/** + * 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. + */ +function copy_to_stream( + 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); + } + } +} + +/** + * Calculate a hash of a Stream + * + * @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. + */ +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; +} + +/** + * Read a line from the stream up to the maximum allowed buffer length + * + * @param StreamInterface $stream Stream to read from + * @param int $maxLength Maximum buffer length + * + * @return string + */ +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; +} + +/** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + * + * @return Request + */ +function parse_request($message) +{ + $data = _parse_message($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] === '/' ? _parse_request_uri($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 + */ +function parse_response($message) +{ + $data = _parse_message($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( + $parts[1], + $data['headers'], + $data['body'], + explode('/', $parts[0])[1], + isset($parts[2]) ? $parts[2] : null + ); +} + +/** + * 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']). + * + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded + * + * @return array + */ +function parse_query($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_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 + */ +function build_query(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) : ''; +} + +/** + * Determines the mimetype of a file by looking at its extension. + * + * @param $filename + * + * @return null|string + */ +function mimetype_from_filename($filename) +{ + return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION)); +} + +/** + * 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 + */ +function mimetype_from_extension($extension) +{ + static $mimetypes = [ + '3gp' => '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; +} + +/** + * 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 + */ +function _parse_message($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 + * @internal + */ +function _parse_request_uri($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, '/'); +} + +/** + * 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 null|string + */ +function get_message_body_summary(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]/', $summary)) { + return null; + } + + return $summary; +} + +/** @internal */ +function _caseless_remove($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; +} 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 @@ +retrieve('file://' . realpath('schema.json')); +$data = json_decode(file_get_contents('data.json')); + +// If you use $ref or if you are unsure, resolve those references here +// This modifies the $schema object +$refResolver = new JsonSchema\RefResolver($retriever); +$refResolver->resolve($schema, 'file://' . __DIR__); + +// Validate +$validator = new JsonSchema\Validator(); +$validator->check($data, $schema); + +if ($validator->isValid()) { + echo "The supplied JSON validates against the schema.\n"; +} else { + echo "JSON does not validate. Violations:\n"; + foreach ($validator->getErrors() as $error) { + echo sprintf("[%s] %s\n", $error['property'], $error['message']); + } +} +``` + +## Running the tests + + $ vendor/bin/phpunit diff --git a/vendor/justinrainbow/json-schema/bin/validate-json b/vendor/justinrainbow/json-schema/bin/validate-json new file mode 100644 index 0000000..e93d53a --- /dev/null +++ b/vendor/justinrainbow/json-schema/bin/validate-json @@ -0,0 +1,245 @@ +#!/usr/bin/env php + + */ + +/** + * Dead simple autoloader + * + * @param string $className Name of class to load + * + * @return void + */ +function __autoload($className) +{ + $className = ltrim($className, '\\'); + $fileName = ''; + $namespace = ''; + if ($lastNsPos = strrpos($className, '\\')) { + $namespace = substr($className, 0, $lastNsPos); + $className = substr($className, $lastNsPos + 1); + $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; + } + $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; + if (stream_resolve_include_path($fileName)) { + require_once $fileName; + } +} + +/** + * Show the json parse error that happened last + * + * @return void + */ +function showJsonError() +{ + $constants = get_defined_constants(true); + $json_errors = array(); + foreach ($constants['json'] as $name => $value) { + if (!strncmp($name, 'JSON_ERROR_', 11)) { + $json_errors[$value] = $name; + } + } + + echo 'JSON parse error: ' . $json_errors[json_last_error()] . "\n"; +} + +function getUrlFromPath($path) +{ + if (parse_url($path, PHP_URL_SCHEME) !== null) { + //already an URL + return $path; + } + if ($path{0} == '/') { + //absolute path + return 'file://' . $path; + } + + //relative path: make absolute + return 'file://' . getcwd() . '/' . $path; +} + +/** + * Take a HTTP header value and split it up into parts. + * + * @return array Key "_value" contains the main value, all others + * as given in the header value + */ +function parseHeaderValue($headerValue) +{ + if (strpos($headerValue, ';') === false) { + return array('_value' => $headerValue); + } + + $parts = explode(';', $headerValue); + $arData = array('_value' => array_shift($parts)); + foreach ($parts as $part) { + list($name, $value) = explode('=', $part); + $arData[$name] = trim($value, ' "\''); + } + return $arData; +} + + +// support running this tool from git checkout +if (is_dir(__DIR__ . '/../src/JsonSchema')) { + set_include_path(__DIR__ . '/../src' . PATH_SEPARATOR . get_include_path()); +} + +$arOptions = array(); +$arArgs = array(); +array_shift($argv);//script itself +foreach ($argv as $arg) { + if ($arg{0} == '-') { + $arOptions[$arg] = true; + } else { + $arArgs[] = $arg; + } +} + +if (count($arArgs) == 0 + || isset($arOptions['--help']) || isset($arOptions['-h']) +) { + echo << array( + 'header' => array( + 'Accept: */*', + 'Connection: Close' + ), + 'max_redirects' => 5 + ) + ) +); +$dataString = file_get_contents($pathData, false, $context); +if ($dataString == '') { + echo "Data file is not readable or empty.\n"; + exit(3); +} + +$data = json_decode($dataString); +unset($dataString); +if ($data === null) { + echo "Error loading JSON data file\n"; + showJsonError(); + exit(5); +} + +if ($pathSchema === null) { + if (isset($http_response_header)) { + array_shift($http_response_header);//HTTP/1.0 line + foreach ($http_response_header as $headerLine) { + list($hName, $hValue) = explode(':', $headerLine, 2); + $hName = strtolower($hName); + if ($hName == 'link') { + //Link: ; rel="describedBy" + $hParts = parseHeaderValue($hValue); + if (isset($hParts['rel']) && $hParts['rel'] == 'describedBy') { + $pathSchema = trim($hParts['_value'], ' <>'); + } + } else if ($hName == 'content-type') { + //Content-Type: application/my-media-type+json; + // profile=http://example.org/schema# + $hParts = parseHeaderValue($hValue); + if (isset($hParts['profile'])) { + $pathSchema = $hParts['profile']; + } + + } + } + } + if (is_object($data) && property_exists($data, '$schema')) { + $pathSchema = $data->{'$schema'}; + } + + //autodetect schema + if ($pathSchema === null) { + echo "JSON data must be an object and have a \$schema property.\n"; + echo "You can pass the schema file on the command line as well.\n"; + echo "Schema autodetection failed.\n"; + exit(6); + } +} +if ($pathSchema{0} == '/') { + $pathSchema = 'file://' . $pathSchema; +} + +$resolver = new JsonSchema\Uri\UriResolver(); +$retriever = new JsonSchema\Uri\UriRetriever(); +try { + $urlSchema = $resolver->resolve($pathSchema, $urlData); + + if (isset($arOptions['--dump-schema-url'])) { + echo $urlSchema . "\n"; + exit(); + } + + $schema = $retriever->retrieve($urlSchema); + if ($schema === null) { + echo "Error loading JSON schema file\n"; + echo $urlSchema . "\n"; + showJsonError(); + exit(2); + } +} catch (Exception $e) { + echo "Error loading JSON schema file\n"; + echo $urlSchema . "\n"; + echo $e->getMessage() . "\n"; + exit(2); +} +$refResolver = new JsonSchema\RefResolver($retriever); +$refResolver->resolve($schema, $urlSchema); + +if (isset($arOptions['--dump-schema'])) { + $options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; + echo json_encode($schema, $options) . "\n"; + exit(); +} + +try { + $validator = new JsonSchema\Validator(); + $validator->check($data, $schema); + + if ($validator->isValid()) { + echo "OK. The supplied JSON validates against the schema.\n"; + } else { + echo "JSON does not validate. Violations:\n"; + foreach ($validator->getErrors() as $error) { + echo sprintf("[%s] %s\n", $error['property'], $error['message']); + } + exit(23); + } +} catch (Exception $e) { + echo "JSON does not validate. Error:\n"; + echo $e->getMessage() . "\n"; + echo "Error code: " . $e->getCode() . "\n"; + exit(24); +} +?> diff --git a/vendor/justinrainbow/json-schema/composer.json b/vendor/justinrainbow/json-schema/composer.json new file mode 100644 index 0000000..68b3933 --- /dev/null +++ b/vendor/justinrainbow/json-schema/composer.json @@ -0,0 +1,58 @@ +{ + "name": "justinrainbow/json-schema", + "description": "A library to validate a json schema.", + "keywords": ["json", "schema"], + "homepage": "https://github.com/justinrainbow/json-schema", + "type": "library", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "repositories": [{ + "type": "package", + "package": { + "name": "json-schema/JSON-Schema-Test-Suite", + "version": "1.1.0", + "source": { + "url": "https://github.com/json-schema/JSON-Schema-Test-Suite", + "type": "git", + "reference": "1.1.0" + } + } + }], + "require": { + "php": ">=5.3.29" + }, + "require-dev": { + "json-schema/JSON-Schema-Test-Suite": "1.1.0", + "phpunit/phpunit": "~3.7", + "phpdocumentor/phpdocumentor": "~2" + }, + "autoload": { + "psr-4": { "JsonSchema\\": "src/JsonSchema/" } + }, + "autoload-dev": { + "psr-4": { "JsonSchema\\Tests\\": "tests/JsonSchema/Tests/" } + }, + "bin": ["bin/validate-json"], + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + } +} diff --git a/vendor/justinrainbow/json-schema/phpunit.xml.dist b/vendor/justinrainbow/json-schema/phpunit.xml.dist new file mode 100644 index 0000000..0136d8e --- /dev/null +++ b/vendor/justinrainbow/json-schema/phpunit.xml.dist @@ -0,0 +1,26 @@ + + + + + + tests + + + + + + ./src/JsonSchema/ + + + diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php new file mode 100644 index 0000000..b43bace --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php @@ -0,0 +1,112 @@ + + * @author Bruno Prieto Reis + */ +class CollectionConstraint extends Constraint +{ + /** + * {@inheritDoc} + */ + public function check($value, $schema = null, $path = null, $i = null) + { + // Verify minItems + if (isset($schema->minItems) && count($value) < $schema->minItems) { + $this->addError($path, "There must be a minimum of " . $schema->minItems . " items in the array", 'minItems', array('minItems' => $schema->minItems,)); + } + + // Verify maxItems + if (isset($schema->maxItems) && count($value) > $schema->maxItems) { + $this->addError($path, "There must be a maximum of " . $schema->maxItems . " items in the array", 'maxItems', array('maxItems' => $schema->maxItems,)); + } + + // Verify uniqueItems + if (isset($schema->uniqueItems) && $schema->uniqueItems) { + $unique = $value; + if (is_array($value) && count($value)) { + $unique = array_map(function($e) { return var_export($e, true); }, $value); + } + if (count(array_unique($unique)) != count($value)) { + $this->addError($path, "There are no duplicates allowed in the array", 'uniqueItems'); + } + } + + // Verify items + if (isset($schema->items)) { + $this->validateItems($value, $schema, $path, $i); + } + } + + /** + * Validates the items + * + * @param array $value + * @param \stdClass $schema + * @param string $path + * @param string $i + */ + protected function validateItems($value, $schema = null, $path = null, $i = null) + { + if (is_object($schema->items)) { + // just one type definition for the whole array + foreach ($value as $k => $v) { + $initErrors = $this->getErrors(); + + // First check if its defined in "items" + $this->checkUndefined($v, $schema->items, $path, $k); + + // Recheck with "additionalItems" if the first test fails + if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) { + $secondErrors = $this->getErrors(); + $this->checkUndefined($v, $schema->additionalItems, $path, $k); + } + + // Reset errors if needed + if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) { + $this->errors = $secondErrors; + } else if (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) { + $this->errors = $initErrors; + } + } + } else { + // Defined item type definitions + foreach ($value as $k => $v) { + if (array_key_exists($k, $schema->items)) { + $this->checkUndefined($v, $schema->items[$k], $path, $k); + } else { + // Additional items + if (property_exists($schema, 'additionalItems')) { + if ($schema->additionalItems !== false) { + $this->checkUndefined($v, $schema->additionalItems, $path, $k); + } else { + $this->addError( + $path, 'The item ' . $i . '[' . $k . '] is not defined and the definition does not allow additional items', 'additionalItems', array('additionalItems' => $schema->additionalItems,)); + } + } else { + // Should be valid against an empty schema + $this->checkUndefined($v, new \stdClass(), $path, $k); + } + } + } + + // Treat when we have more schema definitions than values, not for empty arrays + if(count($value) > 0) { + for ($k = count($value); $k < count($schema->items); $k++) { + $this->checkUndefined(new UndefinedConstraint(), $schema->items[$k], $path, $k); + } + } + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php new file mode 100644 index 0000000..cb3ee80 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php @@ -0,0 +1,291 @@ + + * @author Bruno Prieto Reis + */ +abstract class Constraint implements ConstraintInterface +{ + protected $checkMode = self::CHECK_MODE_NORMAL; + protected $uriRetriever; + protected $errors = array(); + protected $inlineSchemaProperty = '$schema'; + + const CHECK_MODE_NORMAL = 1; + const CHECK_MODE_TYPE_CAST = 2; + + /** + * @var null|Factory + */ + private $factory; + + /** + * @param int $checkMode + * @param UriRetriever $uriRetriever + * @param Factory $factory + */ + public function __construct($checkMode = self::CHECK_MODE_NORMAL, UriRetriever $uriRetriever = null, Factory $factory = null) + { + $this->checkMode = $checkMode; + $this->uriRetriever = $uriRetriever; + $this->factory = $factory; + } + + /** + * @return UriRetriever $uriRetriever + */ + public function getUriRetriever() + { + if (is_null($this->uriRetriever)) + { + $this->setUriRetriever(new UriRetriever); + } + + return $this->uriRetriever; + } + + /** + * @return Factory + */ + public function getFactory() + { + if (!$this->factory) { + $this->factory = new Factory($this->getUriRetriever()); + } + + return $this->factory; + } + + /** + * @param UriRetriever $uriRetriever + */ + public function setUriRetriever(UriRetriever $uriRetriever) + { + $this->uriRetriever = $uriRetriever; + } + + /** + * {@inheritDoc} + */ + public function addError($path, $message, $constraint='', array $more=null) + { + $error = array( + 'property' => $path, + 'message' => $message, + 'constraint' => $constraint, + ); + + if (is_array($more) && count($more) > 0) + { + $error += $more; + } + + $this->errors[] = $error; + } + + /** + * {@inheritDoc} + */ + public function addErrors(array $errors) + { + $this->errors = array_merge($this->errors, $errors); + } + + /** + * {@inheritDoc} + */ + public function getErrors() + { + return $this->errors; + } + + /** + * {@inheritDoc} + */ + public function isValid() + { + return !$this->getErrors(); + } + + /** + * Clears any reported errors. Should be used between + * multiple validation checks. + */ + public function reset() + { + $this->errors = array(); + } + + /** + * Bubble down the path + * + * @param string $path Current path + * @param mixed $i What to append to the path + * + * @return string + */ + protected function incrementPath($path, $i) + { + if ($path !== '') { + if (is_int($i)) { + $path .= '[' . $i . ']'; + } elseif ($i == '') { + $path .= ''; + } else { + $path .= '.' . $i; + } + } else { + $path = $i; + } + + return $path; + } + + /** + * Validates an array + * + * @param mixed $value + * @param mixed $schema + * @param mixed $path + * @param mixed $i + */ + protected function checkArray($value, $schema = null, $path = null, $i = null) + { + $validator = $this->getFactory()->createInstanceFor('collection'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * Validates an object + * + * @param mixed $value + * @param mixed $schema + * @param mixed $path + * @param mixed $i + * @param mixed $patternProperties + */ + protected function checkObject($value, $schema = null, $path = null, $i = null, $patternProperties = null) + { + $validator = $this->getFactory()->createInstanceFor('object'); + $validator->check($value, $schema, $path, $i, $patternProperties); + + $this->addErrors($validator->getErrors()); + } + + /** + * Validates the type of a property + * + * @param mixed $value + * @param mixed $schema + * @param mixed $path + * @param mixed $i + */ + protected function checkType($value, $schema = null, $path = null, $i = null) + { + $validator = $this->getFactory()->createInstanceFor('type'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * Checks a undefined element + * + * @param mixed $value + * @param mixed $schema + * @param mixed $path + * @param mixed $i + */ + protected function checkUndefined($value, $schema = null, $path = null, $i = null) + { + $validator = $this->getFactory()->createInstanceFor('undefined'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * Checks a string element + * + * @param mixed $value + * @param mixed $schema + * @param mixed $path + * @param mixed $i + */ + protected function checkString($value, $schema = null, $path = null, $i = null) + { + $validator = $this->getFactory()->createInstanceFor('string'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * Checks a number element + * + * @param mixed $value + * @param mixed $schema + * @param mixed $path + * @param mixed $i + */ + protected function checkNumber($value, $schema = null, $path = null, $i = null) + { + $validator = $this->getFactory()->createInstanceFor('number'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * Checks a enum element + * + * @param mixed $value + * @param mixed $schema + * @param mixed $path + * @param mixed $i + */ + protected function checkEnum($value, $schema = null, $path = null, $i = null) + { + $validator = $this->getFactory()->createInstanceFor('enum'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + protected function checkFormat($value, $schema = null, $path = null, $i = null) + { + $validator = $this->getFactory()->createInstanceFor('format'); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } + + /** + * @param string $uri JSON Schema URI + * @return string JSON Schema contents + */ + protected function retrieveUri($uri) + { + if (null === $this->uriRetriever) { + $this->setUriRetriever(new UriRetriever); + } + $jsonSchema = $this->uriRetriever->retrieve($uri); + // TODO validate using schema + return $jsonSchema; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php new file mode 100644 index 0000000..34280f4 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php @@ -0,0 +1,60 @@ + + */ +interface ConstraintInterface +{ + /** + * returns all collected errors + * + * @return array + */ + public function getErrors(); + + /** + * adds errors to this validator + * + * @param array $errors + */ + public function addErrors(array $errors); + + /** + * adds an error + * + * @param string $path + * @param string $message + * @param string $constraint the constraint/rule that is broken, e.g.: 'minLength' + * @param array $more more array elements to add to the error + */ + public function addError($path, $message, $constraint='', array $more=null); + + /** + * checks if the validator has not raised errors + * + * @return boolean + */ + public function isValid(); + + /** + * invokes the validation of an element + * + * @abstract + * @param mixed $value + * @param mixed $schema + * @param mixed $path + * @param mixed $i + */ + public function check($value, $schema = null, $path = null, $i = null); +} \ No newline at end of file diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php new file mode 100644 index 0000000..df413e4 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php @@ -0,0 +1,46 @@ + + * @author Bruno Prieto Reis + */ +class EnumConstraint extends Constraint +{ + /** + * {@inheritDoc} + */ + public function check($element, $schema = null, $path = null, $i = null) + { + // Only validate enum if the attribute exists + if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) { + return; + } + + foreach ($schema->enum as $enum) { + $type = gettype($element); + if ($type === gettype($enum)) { + if ($type == "object") { + if ($element == $enum) + return; + } else { + if ($element === $enum) + return; + + } + } + } + + $this->addError($path, "Does not have a value in the enumeration " . print_r($schema->enum, true), 'enum', array('enum' => $schema->enum,)); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php new file mode 100644 index 0000000..8cd25c1 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php @@ -0,0 +1,96 @@ + 'JsonSchema\Constraints\CollectionConstraint', + 'collection' => 'JsonSchema\Constraints\CollectionConstraint', + 'object' => 'JsonSchema\Constraints\ObjectConstraint', + 'type' => 'JsonSchema\Constraints\TypeConstraint', + 'undefined' => 'JsonSchema\Constraints\UndefinedConstraint', + 'string' => 'JsonSchema\Constraints\StringConstraint', + 'number' => 'JsonSchema\Constraints\NumberConstraint', + 'enum' => 'JsonSchema\Constraints\EnumConstraint', + 'format' => 'JsonSchema\Constraints\FormatConstraint', + 'schema' => 'JsonSchema\Constraints\SchemaConstraint', + 'validator' => 'JsonSchema\Validator', + ); + + /** + * @param UriRetriever $uriRetriever + */ + public function __construct(UriRetriever $uriRetriever = null) + { + if (!$uriRetriever) { + $uriRetriever = new UriRetriever(); + } + + $this->uriRetriever = $uriRetriever; + } + + /** + * @return UriRetriever + */ + public function getUriRetriever() + { + return $this->uriRetriever; + } + + /** + * @param string $name + * @param string $class + * @return Factory + */ + public function setConstraintClass($name, $class) + { + // Ensure class exists + if (!class_exists($class)) { + throw new InvalidArgumentException('Unknown constraint ' . $name); + } + // Ensure class is appropriate + if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) { + throw new InvalidArgumentException('Invalid class ' . $name); + } + $this->constraintMap[$name] = $class; + return $this; + } + + /** + * Create a constraint instance for the given constraint name. + * + * @param string $constraintName + * @return ConstraintInterface|ObjectConstraint + * @throws InvalidArgumentException if is not possible create the constraint instance. + */ + public function createInstanceFor($constraintName) + { + if (array_key_exists($constraintName, $this->constraintMap)) { + return new $this->constraintMap[$constraintName](Constraint::CHECK_MODE_NORMAL, $this->uriRetriever, $this); + } + throw new InvalidArgumentException('Unknown constraint ' . $constraintName); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php new file mode 100644 index 0000000..c789753 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php @@ -0,0 +1,181 @@ + + * @link http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23 + */ +class FormatConstraint extends Constraint +{ + /** + * {@inheritDoc} + */ + public function check($element, $schema = null, $path = null, $i = null) + { + if (!isset($schema->format)) { + return; + } + + switch ($schema->format) { + case 'date': + if (!$date = $this->validateDateTime($element, 'Y-m-d')) { + $this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)), 'format', array('format' => $schema->format,)); + } + break; + + case 'time': + if (!$this->validateDateTime($element, 'H:i:s')) { + $this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)), 'format', array('format' => $schema->format,)); + } + break; + + case 'date-time': + if (!$this->validateDateTime($element, 'Y-m-d\TH:i:s\Z') && + !$this->validateDateTime($element, 'Y-m-d\TH:i:s.u\Z') && + !$this->validateDateTime($element, 'Y-m-d\TH:i:sP') && + !$this->validateDateTime($element, 'Y-m-d\TH:i:sO') + ) { + $this->addError($path, sprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', json_encode($element)), 'format', array('format' => $schema->format,)); + } + break; + + case 'utc-millisec': + if (!$this->validateDateTime($element, 'U')) { + $this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)), 'format', array('format' => $schema->format,)); + } + break; + + case 'regex': + if (!$this->validateRegex($element)) { + $this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format,)); + } + break; + + case 'color': + if (!$this->validateColor($element)) { + $this->addError($path, "Invalid color", 'format', array('format' => $schema->format,)); + } + break; + + case 'style': + if (!$this->validateStyle($element)) { + $this->addError($path, "Invalid style", 'format', array('format' => $schema->format,)); + } + break; + + case 'phone': + if (!$this->validatePhone($element)) { + $this->addError($path, "Invalid phone number", 'format', array('format' => $schema->format,)); + } + break; + + case 'uri': + if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { + $this->addError($path, "Invalid URL format", 'format', array('format' => $schema->format,)); + } + break; + + case 'email': + if (null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE)) { + $this->addError($path, "Invalid email", 'format', array('format' => $schema->format,)); + } + break; + + case 'ip-address': + case 'ipv4': + if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) { + $this->addError($path, "Invalid IP address", 'format', array('format' => $schema->format,)); + } + break; + + case 'ipv6': + if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) { + $this->addError($path, "Invalid IP address", 'format', array('format' => $schema->format,)); + } + break; + + case 'host-name': + case 'hostname': + if (!$this->validateHostname($element)) { + $this->addError($path, "Invalid hostname", 'format', array('format' => $schema->format,)); + } + break; + + default: + // Empty as it should be: + // The value of this keyword is called a format attribute. It MUST be a string. + // A format attribute can generally only validate a given set of instance types. + // If the type of the instance to validate is not in this set, validation for + // this format attribute and instance SHOULD succeed. + // http://json-schema.org/latest/json-schema-validation.html#anchor105 + break; + } + } + + protected function validateDateTime($datetime, $format) + { + $dt = \DateTime::createFromFormat($format, $datetime); + + if (!$dt) { + return false; + } + + if ($datetime === $dt->format($format)) { + return true; + } + + // handles the case where a non-6 digit microsecond datetime is passed + // which will fail the above string comparison because the passed + // $datetime may be '2000-05-01T12:12:12.123Z' but format() will return + // '2000-05-01T12:12:12.123000Z' + if ((strpos('u', $format) !== -1) && (intval($dt->format('u')) > 0)) { + return true; + } + + return false; + } + + protected function validateRegex($regex) + { + return false !== @preg_match('/' . $regex . '/', ''); + } + + protected function validateColor($color) + { + if (in_array(strtolower($color), array('aqua', 'black', 'blue', 'fuchsia', + 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', + 'red', 'silver', 'teal', 'white', 'yellow'))) { + return true; + } + + return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color); + } + + protected function validateStyle($style) + { + $properties = explode(';', rtrim($style, ';')); + $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); + + return empty($invalidEntries); + } + + protected function validatePhone($phone) + { + return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone); + } + + protected function validateHostname($host) + { + return preg_match('/^[_a-z]+\.([_a-z]+\.?)+$/i', $host); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php new file mode 100644 index 0000000..c5aaf6a --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php @@ -0,0 +1,83 @@ + + * @author Bruno Prieto Reis + */ +class NumberConstraint extends Constraint +{ + /** + * {@inheritDoc} + */ + public function check($element, $schema = null, $path = null, $i = null) + { + // Verify minimum + if (isset($schema->exclusiveMinimum)) { + if (isset($schema->minimum)) { + if ($schema->exclusiveMinimum && $element <= $schema->minimum) { + $this->addError($path, "Must have a minimum value of " . $schema->minimum, 'exclusiveMinimum', array('minimum' => $schema->minimum,)); + } else if ($element < $schema->minimum) { + $this->addError($path, "Must have a minimum value of " . $schema->minimum, 'minimum', array('minimum' => $schema->minimum,)); + } + } else { + $this->addError($path, "Use of exclusiveMinimum requires presence of minimum", 'missingMinimum'); + } + } else if (isset($schema->minimum) && $element < $schema->minimum) { + $this->addError($path, "Must have a minimum value of " . $schema->minimum, 'minimum', array('minimum' => $schema->minimum,)); + } + + // Verify maximum + if (isset($schema->exclusiveMaximum)) { + if (isset($schema->maximum)) { + if ($schema->exclusiveMaximum && $element >= $schema->maximum) { + $this->addError($path, "Must have a maximum value of " . $schema->maximum, 'exclusiveMaximum', array('maximum' => $schema->maximum,)); + } else if ($element > $schema->maximum) { + $this->addError($path, "Must have a maximum value of " . $schema->maximum, 'maximum', array('maximum' => $schema->maximum,)); + } + } else { + $this->addError($path, "Use of exclusiveMaximum requires presence of maximum", 'missingMinimum'); + } + } else if (isset($schema->maximum) && $element > $schema->maximum) { + $this->addError($path, "Must have a maximum value of " . $schema->maximum, 'maximum', array('maximum' => $schema->maximum,)); + } + + // Verify divisibleBy - Draft v3 + if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) { + $this->addError($path, "Is not divisible by " . $schema->divisibleBy, 'divisibleBy', array('divisibleBy' => $schema->divisibleBy,)); + } + + // Verify multipleOf - Draft v4 + if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) { + $this->addError($path, "Must be a multiple of " . $schema->multipleOf, 'multipleOf', array('multipleOf' => $schema->multipleOf,)); + } + + $this->checkFormat($element, $schema, $path, $i); + } + + private function fmod($number1, $number2) + { + $modulus = fmod($number1, $number2); + $precision = abs(0.0000000001); + $diff = (float)($modulus - $number2); + + if (-$precision < $diff && $diff < $precision) { + return 0.0; + } + + $decimals1 = mb_strpos($number1, ".") ? mb_strlen($number1) - mb_strpos($number1, ".") - 1 : 0; + $decimals2 = mb_strpos($number2, ".") ? mb_strlen($number2) - mb_strpos($number2, ".") - 1 : 0; + + return (float)round($modulus, max($decimals1, $decimals2)); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php new file mode 100644 index 0000000..0e5cf1b --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php @@ -0,0 +1,149 @@ + + * @author Bruno Prieto Reis + */ +class ObjectConstraint extends Constraint +{ + /** + * {@inheritDoc} + */ + function check($element, $definition = null, $path = null, $additionalProp = null, $patternProperties = null) + { + if ($element instanceof UndefinedConstraint) { + return; + } + + $matches = array(); + if ($patternProperties) { + $matches = $this->validatePatternProperties($element, $path, $patternProperties); + } + + if ($definition) { + // validate the definition properties + $this->validateDefinition($element, $definition, $path); + } + + // additional the element properties + $this->validateElement($element, $matches, $definition, $path, $additionalProp); + } + + public function validatePatternProperties($element, $path, $patternProperties) + { + $try = array('/','#','+','~','%'); + $matches = array(); + foreach ($patternProperties as $pregex => $schema) { + $delimiter = '/'; + // Choose delimiter. Necessary for patterns like ^/ , otherwise you get error + foreach ($try as $delimiter) { + if (strpos($pregex, $delimiter) === false) { // safe to use + break; + } + } + + // Validate the pattern before using it to test for matches + if (@preg_match($delimiter. $pregex . $delimiter, '') === false) { + $this->addError($path, 'The pattern "' . $pregex . '" is invalid', 'pregex', array('pregex' => $pregex,)); + continue; + } + foreach ($element as $i => $value) { + if (preg_match($delimiter . $pregex . $delimiter, $i)) { + $matches[] = $i; + $this->checkUndefined($value, $schema ? : new \stdClass(), $path, $i); + } + } + } + return $matches; + } + + /** + * Validates the element properties + * + * @param \stdClass $element Element to validate + * @param array $matches Matches from patternProperties (if any) + * @param \stdClass $objectDefinition ObjectConstraint definition + * @param string $path Path to test? + * @param mixed $additionalProp Additional properties + */ + public function validateElement($element, $matches, $objectDefinition = null, $path = null, $additionalProp = null) + { + foreach ($element as $i => $value) { + + $property = $this->getProperty($element, $i, new UndefinedConstraint()); + $definition = $this->getProperty($objectDefinition, $i); + + // no additional properties allowed + if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) { + $this->addError($path, "The property " . $i . " is not defined and the definition does not allow additional properties", 'additionalProp'); + } + + // additional properties defined + if (!in_array($i, $matches) && $additionalProp && !$definition) { + if ($additionalProp === true) { + $this->checkUndefined($value, null, $path, $i); + } else { + $this->checkUndefined($value, $additionalProp, $path, $i); + } + } + + // property requires presence of another + $require = $this->getProperty($definition, 'requires'); + if ($require && !$this->getProperty($element, $require)) { + $this->addError($path, "The presence of the property " . $i . " requires that " . $require . " also be present", 'requires'); + } + + if (!$definition) { + // normal property verification + $this->checkUndefined($value, new \stdClass(), $path, $i); + } + } + } + + /** + * Validates the definition properties + * + * @param \stdClass $element Element to validate + * @param \stdClass $objectDefinition ObjectConstraint definition + * @param string $path Path? + */ + public function validateDefinition($element, $objectDefinition = null, $path = null) + { + foreach ($objectDefinition as $i => $value) { + $property = $this->getProperty($element, $i, new UndefinedConstraint()); + $definition = $this->getProperty($objectDefinition, $i); + $this->checkUndefined($property, $definition, $path, $i); + } + } + + /** + * retrieves a property from an object or array + * + * @param mixed $element Element to validate + * @param string $property Property to retrieve + * @param mixed $fallback Default value if property is not found + * + * @return mixed + */ + protected function getProperty($element, $property, $fallback = null) + { + if (is_array($element) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) { + return array_key_exists($property, $element) ? $element[$property] : $fallback; + } elseif (is_object($element)) { + return property_exists($element, $property) ? $element->$property : $fallback; + } + + return $fallback; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php new file mode 100644 index 0000000..b856a11 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php @@ -0,0 +1,37 @@ + + * @author Bruno Prieto Reis + */ +class SchemaConstraint extends Constraint +{ + /** + * {@inheritDoc} + */ + public function check($element, $schema = null, $path = null, $i = null) + { + if ($schema !== null) { + // passed schema + $this->checkUndefined($element, $schema, '', ''); + } elseif (property_exists($element, $this->inlineSchemaProperty)) { + // inline schema + $this->checkUndefined($element, $element->{$this->inlineSchemaProperty}, '', ''); + } else { + throw new InvalidArgumentException('no schema found to verify against'); + } + } +} \ No newline at end of file diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php new file mode 100644 index 0000000..f57f64c --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php @@ -0,0 +1,57 @@ + + * @author Bruno Prieto Reis + */ +class StringConstraint extends Constraint +{ + /** + * {@inheritDoc} + */ + public function check($element, $schema = null, $path = null, $i = null) + { + // Verify maxLength + if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) { + $this->addError($path, "Must be at most " . $schema->maxLength . " characters long", 'maxLength', array( + 'maxLength' => $schema->maxLength, + )); + } + + //verify minLength + if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) { + $this->addError($path, "Must be at least " . $schema->minLength . " characters long", 'minLength', array( + 'minLength' => $schema->minLength, + )); + } + + // Verify a regex pattern + if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#', $element)) { + $this->addError($path, "Does not match the regex pattern " . $schema->pattern, 'pattern', array( + 'pattern' => $schema->pattern, + )); + } + + $this->checkFormat($element, $schema, $path, $i); + } + + private function strlen($string) + { + if (extension_loaded('mbstring')) { + return mb_strlen($string, mb_detect_encoding($string)); + } else { + return strlen($string); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php new file mode 100644 index 0000000..837cfd8 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php @@ -0,0 +1,145 @@ + + * @author Bruno Prieto Reis + */ +class TypeConstraint extends Constraint +{ + /** + * @var array|string[] type wordings for validation error messages + */ + static $wording = array( + 'integer' => 'an integer', + 'number' => 'a number', + 'boolean' => 'a boolean', + 'object' => 'an object', + 'array' => 'an array', + 'string' => 'a string', + 'null' => 'a null', + 'any' => NULL, // validation of 'any' is always true so is not needed in message wording + 0 => NULL, // validation of a false-y value is always true, so not needed as well + ); + + /** + * {@inheritDoc} + */ + public function check($value = null, $schema = null, $path = null, $i = null) + { + $type = isset($schema->type) ? $schema->type : null; + $isValid = true; + + if (is_array($type)) { + // @TODO refactor + $validatedOneType = false; + $errors = array(); + foreach ($type as $tp) { + $validator = new TypeConstraint($this->checkMode); + $subSchema = new \stdClass(); + $subSchema->type = $tp; + $validator->check($value, $subSchema, $path, null); + $error = $validator->getErrors(); + + if (!count($error)) { + $validatedOneType = true; + break; + } + + $errors = $error; + } + + if (!$validatedOneType) { + $this->addErrors($errors); + + return; + } + } elseif (is_object($type)) { + $this->checkUndefined($value, $type, $path); + } else { + $isValid = $this->validateType($value, $type); + } + + if ($isValid === false) { + if (!isset(self::$wording[$type])) { + throw new StandardUnexpectedValueException( + sprintf( + "No wording for %s available, expected wordings are: [%s]", + var_export($type, true), + implode(', ', array_filter(self::$wording))) + ); + } + $this->addError($path, ucwords(gettype($value)) . " value found, but " . self::$wording[$type] . " is required", 'type'); + } + } + + /** + * Verifies that a given value is of a certain type + * + * @param mixed $value Value to validate + * @param string $type TypeConstraint to check against + * + * @return boolean + * + * @throws InvalidArgumentException + */ + protected function validateType($value, $type) + { + //mostly the case for inline schema + if (!$type) { + return true; + } + + if ('integer' === $type) { + return is_int($value); + } + + if ('number' === $type) { + return is_numeric($value) && !is_string($value); + } + + if ('boolean' === $type) { + return is_bool($value); + } + + if ('object' === $type) { + return is_object($value); + //return ($this::CHECK_MODE_TYPE_CAST == $this->checkMode) ? is_array($value) : is_object($value); + } + + if ('array' === $type) { + return is_array($value); + } + + if ('string' === $type) { + return is_string($value); + } + + if ('email' === $type) { + return is_string($value); + } + + if ('null' === $type) { + return is_null($value); + } + + if ('any' === $type) { + return true; + } + + throw new InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php new file mode 100644 index 0000000..c033720 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php @@ -0,0 +1,307 @@ + + * @author Bruno Prieto Reis + */ +class UndefinedConstraint extends Constraint +{ + /** + * {@inheritDoc} + */ + public function check($value, $schema = null, $path = null, $i = null) + { + if (is_null($schema)) { + return; + } + + if (!is_object($schema)) { + throw new InvalidArgumentException( + 'Given schema must be an object in ' . $path + . ' but is a ' . gettype($schema) + ); + } + + $i = is_null($i) ? "" : $i; + $path = $this->incrementPath($path, $i); + + // check special properties + $this->validateCommonProperties($value, $schema, $path); + + // check allOf, anyOf, and oneOf properties + $this->validateOfProperties($value, $schema, $path); + + // check known types + $this->validateTypes($value, $schema, $path, $i); + } + + /** + * Validates the value against the types + * + * @param mixed $value + * @param mixed $schema + * @param string $path + * @param string $i + */ + public function validateTypes($value, $schema = null, $path = null, $i = null) + { + // check array + if (is_array($value)) { + $this->checkArray($value, $schema, $path, $i); + } + + // check object + if (is_object($value) && (isset($schema->properties) || isset($schema->patternProperties) || isset($schema->additionalProperties))) { + $this->checkObject( + $value, + isset($schema->properties) ? $schema->properties : null, + $path, + isset($schema->additionalProperties) ? $schema->additionalProperties : null, + isset($schema->patternProperties) ? $schema->patternProperties : null + ); + } + + // check string + if (is_string($value)) { + $this->checkString($value, $schema, $path, $i); + } + + // check numeric + if (is_numeric($value)) { + $this->checkNumber($value, $schema, $path, $i); + } + + // check enum + if (isset($schema->enum)) { + $this->checkEnum($value, $schema, $path, $i); + } + } + + /** + * Validates common properties + * + * @param mixed $value + * @param mixed $schema + * @param string $path + * @param string $i + */ + protected function validateCommonProperties($value, $schema = null, $path = null, $i = "") + { + // if it extends another schema, it must pass that schema as well + if (isset($schema->extends)) { + if (is_string($schema->extends)) { + $schema->extends = $this->validateUri($schema, $schema->extends); + } + if (is_array($schema->extends)) { + foreach ($schema->extends as $extends) { + $this->checkUndefined($value, $extends, $path, $i); + } + } else { + $this->checkUndefined($value, $schema->extends, $path, $i); + } + } + + // Verify required values + if (is_object($value)) { + if (!($value instanceof UndefinedConstraint) && isset($schema->required) && is_array($schema->required) ) { + // Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...] + foreach ($schema->required as $required) { + if (!property_exists($value, $required)) { + $this->addError((!$path) ? $required : "$path.$required", "The property " . $required . " is required", 'required'); + } + } + } else if (isset($schema->required) && !is_array($schema->required)) { + // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true} + if ( $schema->required && $value instanceof UndefinedConstraint) { + $this->addError($path, "Is missing and it is required", 'required'); + } + } + } + + // Verify type + if (!($value instanceof UndefinedConstraint)) { + $this->checkType($value, $schema, $path); + } + + // Verify disallowed items + if (isset($schema->disallow)) { + $initErrors = $this->getErrors(); + + $typeSchema = new \stdClass(); + $typeSchema->type = $schema->disallow; + $this->checkType($value, $typeSchema, $path); + + // if no new errors were raised it must be a disallowed value + if (count($this->getErrors()) == count($initErrors)) { + $this->addError($path, "Disallowed value was matched", 'disallow'); + } else { + $this->errors = $initErrors; + } + } + + if (isset($schema->not)) { + $initErrors = $this->getErrors(); + $this->checkUndefined($value, $schema->not, $path, $i); + + // if no new errors were raised then the instance validated against the "not" schema + if (count($this->getErrors()) == count($initErrors)) { + $this->addError($path, "Matched a schema which it should not", 'not'); + } else { + $this->errors = $initErrors; + } + } + + // Verify minimum and maximum number of properties + if (is_object($value)) { + if (isset($schema->minProperties)) { + if (count(get_object_vars($value)) < $schema->minProperties) { + $this->addError($path, "Must contain a minimum of " . $schema->minProperties . " properties", 'minProperties', array('minProperties' => $schema->minProperties,)); + } + } + if (isset($schema->maxProperties)) { + if (count(get_object_vars($value)) > $schema->maxProperties) { + $this->addError($path, "Must contain no more than " . $schema->maxProperties . " properties", 'maxProperties', array('maxProperties' => $schema->maxProperties,)); + } + } + } + + // Verify that dependencies are met + if (is_object($value) && isset($schema->dependencies)) { + $this->validateDependencies($value, $schema->dependencies, $path); + } + } + + /** + * Validate allOf, anyOf, and oneOf properties + * + * @param mixed $value + * @param mixed $schema + * @param string $path + * @param string $i + */ + protected function validateOfProperties($value, $schema, $path, $i = "") + { + // Verify type + if ($value instanceof UndefinedConstraint) { + return; + } + + if (isset($schema->allOf)) { + $isValid = true; + foreach ($schema->allOf as $allOf) { + $initErrors = $this->getErrors(); + $this->checkUndefined($value, $allOf, $path, $i); + $isValid = $isValid && (count($this->getErrors()) == count($initErrors)); + } + if (!$isValid) { + $this->addError($path, "Failed to match all schemas", 'allOf'); + } + } + + if (isset($schema->anyOf)) { + $isValid = false; + $startErrors = $this->getErrors(); + foreach ($schema->anyOf as $anyOf) { + $initErrors = $this->getErrors(); + $this->checkUndefined($value, $anyOf, $path, $i); + if ($isValid = (count($this->getErrors()) == count($initErrors))) { + break; + } + } + if (!$isValid) { + $this->addError($path, "Failed to match at least one schema", 'anyOf'); + } else { + $this->errors = $startErrors; + } + } + + if (isset($schema->oneOf)) { + $allErrors = array(); + $matchedSchemas = 0; + $startErrors = $this->getErrors(); + foreach ($schema->oneOf as $oneOf) { + $this->errors = array(); + $this->checkUndefined($value, $oneOf, $path, $i); + if (count($this->getErrors()) == 0) { + $matchedSchemas++; + } + $allErrors = array_merge($allErrors, array_values($this->getErrors())); + } + if ($matchedSchemas !== 1) { + $this->addErrors( + array_merge( + $allErrors, + array(array( + 'property' => $path, + 'message' => "Failed to match exactly one schema", + 'constraint' => 'oneOf', + ),), + $startErrors + ) + ); + } else { + $this->errors = $startErrors; + } + } + } + + /** + * Validate dependencies + * + * @param mixed $value + * @param mixed $dependencies + * @param string $path + * @param string $i + */ + protected function validateDependencies($value, $dependencies, $path, $i = "") + { + foreach ($dependencies as $key => $dependency) { + if (property_exists($value, $key)) { + if (is_string($dependency)) { + // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"} + if (!property_exists($value, $dependency)) { + $this->addError($path, "$key depends on $dependency and $dependency is missing", 'dependencies'); + } + } else if (is_array($dependency)) { + // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]} + foreach ($dependency as $d) { + if (!property_exists($value, $d)) { + $this->addError($path, "$key depends on $d and $d is missing", 'dependencies'); + } + } + } else if (is_object($dependency)) { + // Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}} + $this->checkUndefined($value, $dependency, $path, $i); + } + } + } + } + + protected function validateUri($schema, $schemaUri = null) + { + $resolver = new UriResolver(); + $retriever = $this->getUriRetriever(); + + $jsonSchema = null; + if ($resolver->isValid($schemaUri)) { + $schemaId = property_exists($schema, 'id') ? $schema->id : null; + $jsonSchema = $retriever->retrieve($schemaId, $schemaUri); + } + + return $jsonSchema; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..ec702a7 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php @@ -0,0 +1,17 @@ + + * @see README.md + */ +class RefResolver +{ + /** + * HACK to prevent too many recursive expansions. + * Happens e.g. when you want to validate a schema against the schema + * definition. + * + * @var integer + */ + protected static $depth = 0; + + /** + * maximum references depth + * @var integer + */ + public static $maxDepth = 7; + + /** + * @var UriRetrieverInterface + */ + protected $uriRetriever = null; + + /** + * @var object + */ + protected $rootSchema = null; + + /** + * @param UriRetriever $retriever + */ + public function __construct($retriever = null) + { + $this->uriRetriever = $retriever; + } + + /** + * Retrieves a given schema given a ref and a source URI + * + * @param string $ref Reference from schema + * @param string $sourceUri URI where original schema was located + * @return object Schema + */ + public function fetchRef($ref, $sourceUri) + { + $retriever = $this->getUriRetriever(); + $jsonSchema = $retriever->retrieve($ref, $sourceUri); + $this->resolve($jsonSchema); + + return $jsonSchema; + } + + /** + * Return the URI Retriever, defaulting to making a new one if one + * was not yet set. + * + * @return UriRetriever + */ + public function getUriRetriever() + { + if (is_null($this->uriRetriever)) { + $this->setUriRetriever(new UriRetriever); + } + + return $this->uriRetriever; + } + + /** + * Resolves all $ref references for a given schema. Recurses through + * the object to resolve references of any child schemas. + * + * The 'format' property is omitted because it isn't required for + * validation. Theoretically, this class could be extended to look + * for URIs in formats: "These custom formats MAY be expressed as + * an URI, and this URI MAY reference a schema of that format." + * + * The 'id' property is not filled in, but that could be made to happen. + * + * @param object $schema JSON Schema to flesh out + * @param string $sourceUri URI where this schema was located + */ + public function resolve($schema, $sourceUri = null) + { + if (self::$depth > self::$maxDepth) { + self::$depth = 0; + throw new JsonDecodingException(JSON_ERROR_DEPTH); + } + ++self::$depth; + + if (! is_object($schema)) { + --self::$depth; + return; + } + + if (null === $sourceUri && ! empty($schema->id)) { + $sourceUri = $schema->id; + } + + if (null === $this->rootSchema) { + $this->rootSchema = $schema; + } + + // Resolve $ref first + $this->resolveRef($schema, $sourceUri); + + // These properties are just schemas + // eg. items can be a schema or an array of schemas + foreach (array('additionalItems', 'additionalProperties', 'extends', 'items') as $propertyName) { + $this->resolveProperty($schema, $propertyName, $sourceUri); + } + + // These are all potentially arrays that contain schema objects + // eg. type can be a value or an array of values/schemas + // eg. items can be a schema or an array of schemas + foreach (array('disallow', 'extends', 'items', 'type', 'allOf', 'anyOf', 'oneOf') as $propertyName) { + $this->resolveArrayOfSchemas($schema, $propertyName, $sourceUri); + } + + // These are all objects containing properties whose values are schemas + foreach (array('dependencies', 'patternProperties', 'properties') as $propertyName) { + $this->resolveObjectOfSchemas($schema, $propertyName, $sourceUri); + } + + --self::$depth; + } + + /** + * Given an object and a property name, that property should be an + * array whose values can be schemas. + * + * @param object $schema JSON Schema to flesh out + * @param string $propertyName Property to work on + * @param string $sourceUri URI where this schema was located + */ + public function resolveArrayOfSchemas($schema, $propertyName, $sourceUri) + { + if (! isset($schema->$propertyName) || ! is_array($schema->$propertyName)) { + return; + } + + foreach ($schema->$propertyName as $possiblySchema) { + $this->resolve($possiblySchema, $sourceUri); + } + } + + /** + * Given an object and a property name, that property should be an + * object whose properties are schema objects. + * + * @param object $schema JSON Schema to flesh out + * @param string $propertyName Property to work on + * @param string $sourceUri URI where this schema was located + */ + public function resolveObjectOfSchemas($schema, $propertyName, $sourceUri) + { + if (! isset($schema->$propertyName) || ! is_object($schema->$propertyName)) { + return; + } + + foreach (get_object_vars($schema->$propertyName) as $possiblySchema) { + $this->resolve($possiblySchema, $sourceUri); + } + } + + /** + * Given an object and a property name, that property should be a + * schema object. + * + * @param object $schema JSON Schema to flesh out + * @param string $propertyName Property to work on + * @param string $sourceUri URI where this schema was located + */ + public function resolveProperty($schema, $propertyName, $sourceUri) + { + if (! isset($schema->$propertyName)) { + return; + } + + $this->resolve($schema->$propertyName, $sourceUri); + } + + /** + * Look for the $ref property in the object. If found, remove the + * reference and augment this object with the contents of another + * schema. + * + * @param object $schema JSON Schema to flesh out + * @param string $sourceUri URI where this schema was located + */ + public function resolveRef($schema, $sourceUri) + { + $ref = '$ref'; + + if (empty($schema->$ref)) { + return; + } + + $splitRef = explode('#', $schema->$ref, 2); + + $refDoc = $splitRef[0]; + $refPath = null; + if (count($splitRef) === 2) { + $refPath = explode('/', $splitRef[1]); + array_shift($refPath); + } + + if (empty($refDoc) && empty($refPath)) { + // TODO: Not yet implemented - root pointer ref, causes recursion issues + return; + } + + if (!empty($refDoc)) { + $refSchema = $this->fetchRef($refDoc, $sourceUri); + } else { + $refSchema = $this->rootSchema; + } + + if (null !== $refPath) { + $refSchema = $this->resolveRefSegment($refSchema, $refPath); + } + + unset($schema->$ref); + + // Augment the current $schema object with properties fetched + foreach (get_object_vars($refSchema) as $prop => $value) { + $schema->$prop = $value; + } + } + + /** + * Set URI Retriever for use with the Ref Resolver + * + * @param UriRetriever $retriever + * @return $this for chaining + */ + public function setUriRetriever(UriRetriever $retriever) + { + $this->uriRetriever = $retriever; + + return $this; + } + + protected function resolveRefSegment($data, $pathParts) + { + foreach ($pathParts as $path) { + $path = strtr($path, array('~1' => '/', '~0' => '~', '%25' => '%')); + + if (is_array($data)) { + $data = $data[$path]; + } else { + $data = $data->{$path}; + } + } + + return $data; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php new file mode 100644 index 0000000..f924ad8 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php @@ -0,0 +1,29 @@ + + */ +abstract class AbstractRetriever implements UriRetrieverInterface +{ + /** + * Media content type + * @var string + */ + protected $contentType; + + /** + * {@inheritDoc} + * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::getContentType() + */ + public function getContentType() + { + return $this->contentType; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php new file mode 100644 index 0000000..cd8414f --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php @@ -0,0 +1,79 @@ + + */ +class Curl extends AbstractRetriever +{ + protected $messageBody; + + public function __construct() + { + if (!function_exists('curl_init')) { + throw new \RuntimeException("cURL not installed"); + } + } + + /** + * {@inheritDoc} + * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() + */ + public function retrieve($uri) + { + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $uri); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: ' . Validator::SCHEMA_MEDIA_TYPE)); + + $response = curl_exec($ch); + if (false === $response) { + throw new \JsonSchema\Exception\ResourceNotFoundException('JSON schema not found'); + } + + $this->fetchMessageBody($response); + $this->fetchContentType($response); + + curl_close($ch); + + return $this->messageBody; + } + + /** + * @param string $response cURL HTTP response + */ + private function fetchMessageBody($response) + { + preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match); + $this->messageBody = $match[1]; + } + + /** + * @param string $response cURL HTTP response + * @return boolean Whether the Content-Type header was found or not + */ + protected function fetchContentType($response) + { + if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) { + $this->contentType = trim($match[1]); + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php new file mode 100644 index 0000000..bc43de6 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php @@ -0,0 +1,87 @@ + + */ +class FileGetContents extends AbstractRetriever +{ + protected $messageBody; + + /** + * {@inheritDoc} + * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() + */ + public function retrieve($uri) + { + $context = stream_context_create(array( + 'http' => array( + 'method' => 'GET', + 'header' => "Accept: " . Validator::SCHEMA_MEDIA_TYPE + ))); + + set_error_handler(function() use ($uri) { + throw new ResourceNotFoundException('JSON schema not found at ' . $uri); + }); + $response = file_get_contents($uri); + restore_error_handler(); + + if (false === $response) { + throw new ResourceNotFoundException('JSON schema not found at ' . $uri); + } + if ($response == '' + && substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/' + ) { + throw new ResourceNotFoundException('JSON schema not found at ' . $uri); + } + + $this->messageBody = $response; + if (! empty($http_response_header)) { + $this->fetchContentType($http_response_header); + } else { + // Could be a "file://" url or something else - fake up the response + $this->contentType = null; + } + + return $this->messageBody; + } + + /** + * @param array $headers HTTP Response Headers + * @return boolean Whether the Content-Type header was found or not + */ + private function fetchContentType(array $headers) + { + foreach ($headers as $header) { + if ($this->contentType = self::getContentTypeMatchInHeader($header)) { + return true; + } + } + + return false; + } + + /** + * @param string $header + * @return string|null + */ + protected static function getContentTypeMatchInHeader($header) + { + if (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) { + return trim($match[1]); + } + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php new file mode 100644 index 0000000..7652c42 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php @@ -0,0 +1,54 @@ + '{ ... }', + * 'http://acme.com/schemas/address#' => '{ ... }', + * )) + * + * $schema = $retriever->retrieve('http://acme.com/schemas/person#'); + */ +class PredefinedArray extends AbstractRetriever +{ + /** + * Contains schemas as URI => JSON + * @var array + */ + private $schemas; + + /** + * Constructor + * + * @param array $schemas + * @param string $contentType + */ + public function __construct(array $schemas, $contentType = Validator::SCHEMA_MEDIA_TYPE) + { + $this->schemas = $schemas; + $this->contentType = $contentType; + } + + /** + * {@inheritDoc} + * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() + */ + public function retrieve($uri) + { + if (!array_key_exists($uri, $this->schemas)) { + throw new \JsonSchema\Exception\ResourceNotFoundException(sprintf( + 'The JSON schema "%s" was not found.', + $uri + )); + } + + return $this->schemas[$uri]; + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php new file mode 100644 index 0000000..c324998 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php @@ -0,0 +1,32 @@ + + */ +interface UriRetrieverInterface +{ + /** + * Retrieve a schema from the specified URI + * @param string $uri URI that resolves to a JSON schema + * @throws \JsonSchema\Exception\ResourceNotFoundException + * @return mixed string|null + */ + public function retrieve($uri); + + /** + * Get media content type + * @return string + */ + public function getContentType(); +} \ No newline at end of file diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php new file mode 100644 index 0000000..9784114 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php @@ -0,0 +1,157 @@ + + */ +class UriResolver +{ + /** + * Parses a URI into five main components + * + * @param string $uri + * @return array + */ + public function parse($uri) + { + preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match); + + $components = array(); + if (5 < count($match)) { + $components = array( + 'scheme' => $match[2], + 'authority' => $match[4], + 'path' => $match[5] + ); + } + if (7 < count($match)) { + $components['query'] = $match[7]; + } + if (9 < count($match)) { + $components['fragment'] = $match[9]; + } + + return $components; + } + + /** + * Builds a URI based on n array with the main components + * + * @param array $components + * @return string + */ + public function generate(array $components) + { + $uri = $components['scheme'] . '://' + . $components['authority'] + . $components['path']; + + if (array_key_exists('query', $components)) { + $uri .= $components['query']; + } + if (array_key_exists('fragment', $components)) { + $uri .= '#' . $components['fragment']; + } + + return $uri; + } + + /** + * Resolves a URI + * + * @param string $uri Absolute or relative + * @param string $baseUri Optional base URI + * @return string Absolute URI + */ + public function resolve($uri, $baseUri = null) + { + if ($uri == '') { + return $baseUri; + } + + $components = $this->parse($uri); + $path = $components['path']; + + if (! empty($components['scheme'])) { + return $uri; + } + $baseComponents = $this->parse($baseUri); + $basePath = $baseComponents['path']; + + $baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath); + if (isset($components['fragment'])) { + $baseComponents['fragment'] = $components['fragment']; + } + + return $this->generate($baseComponents); + } + + /** + * Tries to glue a relative path onto an absolute one + * + * @param string $relativePath + * @param string $basePath + * @return string Merged path + * @throws UriResolverException + */ + public static function combineRelativePathWithBasePath($relativePath, $basePath) + { + $relativePath = self::normalizePath($relativePath); + if ($relativePath == '') { + return $basePath; + } + if ($relativePath{0} == '/') { + return $relativePath; + } + + $basePathSegments = explode('/', $basePath); + + preg_match('|^/?(\.\./(?:\./)*)*|', $relativePath, $match); + $numLevelUp = strlen($match[0]) /3 + 1; + if ($numLevelUp >= count($basePathSegments)) { + throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath)); + } + + $basePathSegments = array_slice($basePathSegments, 0, -$numLevelUp); + $path = preg_replace('|^/?(\.\./(\./)*)*|', '', $relativePath); + + return implode('/', $basePathSegments) . '/' . $path; + } + + /** + * Normalizes a URI path component by removing dot-slash and double slashes + * + * @param string $path + * @return string + */ + private static function normalizePath($path) + { + $path = preg_replace('|((?parse($uri); + + return !empty($components); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php new file mode 100644 index 0000000..c723cd9 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php @@ -0,0 +1,289 @@ + + */ +class UriRetriever +{ + /** + * @var null|UriRetrieverInterface + */ + protected $uriRetriever = null; + + /** + * @var array|object[] + * @see loadSchema + */ + private $schemaCache = array(); + + /** + * Guarantee the correct media type was encountered + * + * @param UriRetrieverInterface $uriRetriever + * @param string $uri + * @return bool|void + */ + public function confirmMediaType($uriRetriever, $uri) + { + $contentType = $uriRetriever->getContentType(); + + if (is_null($contentType)) { + // Well, we didn't get an invalid one + return; + } + + if (Validator::SCHEMA_MEDIA_TYPE === $contentType) { + return; + } + + if (substr($uri, 0, 23) == 'http://json-schema.org/') { + //HACK; they deliver broken content types + return true; + } + + throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE)); + } + + /** + * Get a URI Retriever + * + * If none is specified, sets a default FileGetContents retriever and + * returns that object. + * + * @return UriRetrieverInterface + */ + public function getUriRetriever() + { + if (is_null($this->uriRetriever)) { + $this->setUriRetriever(new FileGetContents); + } + + return $this->uriRetriever; + } + + /** + * Resolve a schema based on pointer + * + * URIs can have a fragment at the end in the format of + * #/path/to/object and we are to look up the 'path' property of + * the first object then the 'to' and 'object' properties. + * + * @param object $jsonSchema JSON Schema contents + * @param string $uri JSON Schema URI + * @return object JSON Schema after walking down the fragment pieces + * + * @throws ResourceNotFoundException + */ + public function resolvePointer($jsonSchema, $uri) + { + $resolver = new UriResolver(); + $parsed = $resolver->parse($uri); + if (empty($parsed['fragment'])) { + return $jsonSchema; + } + + $path = explode('/', $parsed['fragment']); + while ($path) { + $pathElement = array_shift($path); + if (! empty($pathElement)) { + $pathElement = str_replace('~1', '/', $pathElement); + $pathElement = str_replace('~0', '~', $pathElement); + if (! empty($jsonSchema->$pathElement)) { + $jsonSchema = $jsonSchema->$pathElement; + } else { + throw new ResourceNotFoundException( + 'Fragment "' . $parsed['fragment'] . '" not found' + . ' in ' . $uri + ); + } + + if (! is_object($jsonSchema)) { + throw new ResourceNotFoundException( + 'Fragment part "' . $pathElement . '" is no object ' + . ' in ' . $uri + ); + } + } + } + + return $jsonSchema; + } + + /** + * Retrieve a URI + * + * @param string $uri JSON Schema URI + * @param string|null $baseUri + * @return object JSON Schema contents + */ + public function retrieve($uri, $baseUri = null) + { + $resolver = new UriResolver(); + $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri); + + //fetch URL without #fragment + $arParts = $resolver->parse($resolvedUri); + if (isset($arParts['fragment'])) { + unset($arParts['fragment']); + $fetchUri = $resolver->generate($arParts); + } + + $jsonSchema = $this->loadSchema($fetchUri); + + // Use the JSON pointer if specified + $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri); + + if ($jsonSchema instanceof \stdClass) { + $jsonSchema->id = $resolvedUri; + } + + return $jsonSchema; + } + + /** + * Fetch a schema from the given URI, json-decode it and return it. + * Caches schema objects. + * + * @param string $fetchUri Absolute URI + * + * @return object JSON schema object + */ + protected function loadSchema($fetchUri) + { + if (isset($this->schemaCache[$fetchUri])) { + return $this->schemaCache[$fetchUri]; + } + + $uriRetriever = $this->getUriRetriever(); + $contents = $this->uriRetriever->retrieve($fetchUri); + $this->confirmMediaType($uriRetriever, $fetchUri); + $jsonSchema = json_decode($contents); + + if (JSON_ERROR_NONE < $error = json_last_error()) { + throw new JsonDecodingException($error); + } + + $this->schemaCache[$fetchUri] = $jsonSchema; + + return $jsonSchema; + } + + /** + * Set the URI Retriever + * + * @param UriRetrieverInterface $uriRetriever + * @return $this for chaining + */ + public function setUriRetriever(UriRetrieverInterface $uriRetriever) + { + $this->uriRetriever = $uriRetriever; + + return $this; + } + + /** + * Parses a URI into five main components + * + * @param string $uri + * @return array + */ + public function parse($uri) + { + preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match); + + $components = array(); + if (5 < count($match)) { + $components = array( + 'scheme' => $match[2], + 'authority' => $match[4], + 'path' => $match[5] + ); + } + + if (7 < count($match)) { + $components['query'] = $match[7]; + } + + if (9 < count($match)) { + $components['fragment'] = $match[9]; + } + + return $components; + } + + /** + * Builds a URI based on n array with the main components + * + * @param array $components + * @return string + */ + public function generate(array $components) + { + $uri = $components['scheme'] . '://' + . $components['authority'] + . $components['path']; + + if (array_key_exists('query', $components)) { + $uri .= $components['query']; + } + + if (array_key_exists('fragment', $components)) { + $uri .= $components['fragment']; + } + + return $uri; + } + + /** + * Resolves a URI + * + * @param string $uri Absolute or relative + * @param string $baseUri Optional base URI + * @return string + */ + public function resolve($uri, $baseUri = null) + { + $components = $this->parse($uri); + $path = $components['path']; + + if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) { + return $uri; + } + + $baseComponents = $this->parse($baseUri); + $basePath = $baseComponents['path']; + + $baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath); + + return $this->generate($baseComponents); + } + + /** + * @param string $uri + * @return boolean + */ + public function isValid($uri) + { + $components = $this->parse($uri); + + return !empty($components); + } +} diff --git a/vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php b/vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php new file mode 100644 index 0000000..14dbb60 --- /dev/null +++ b/vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php @@ -0,0 +1,40 @@ + + * @author Bruno Prieto Reis + * @see README.md + */ +class Validator extends Constraint +{ + const SCHEMA_MEDIA_TYPE = 'application/schema+json'; + + /** + * Validates the given data against the schema and returns an object containing the results + * Both the php object and the schema are supposed to be a result of a json_decode call. + * The validation works as defined by the schema proposal in http://json-schema.org + * + * {@inheritDoc} + */ + public function check($value, $schema = null, $path = null, $i = null) + { + $validator = $this->getFactory()->createInstanceFor('schema'); + $validator->check($value, $schema); + + $this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR)); + } +} diff --git a/vendor/league/flysystem-cached-adapter/.editorconfig b/vendor/league/flysystem-cached-adapter/.editorconfig new file mode 100644 index 0000000..153cf3e --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/.editorconfig @@ -0,0 +1,10 @@ +; top-most EditorConfig file +root = true + +; Unix-style newlines +[*] +end_of_line = LF + +[*.php] +indent_style = space +indent_size = 4 diff --git a/vendor/league/flysystem-cached-adapter/.gitignore b/vendor/league/flysystem-cached-adapter/.gitignore new file mode 100644 index 0000000..7aea75f --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/.gitignore @@ -0,0 +1,4 @@ +coverage +coverage.xml +composer.lock +vendor \ No newline at end of file diff --git a/vendor/league/flysystem-cached-adapter/.php_cs b/vendor/league/flysystem-cached-adapter/.php_cs new file mode 100644 index 0000000..6643a32 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/.php_cs @@ -0,0 +1,7 @@ +level(Symfony\CS\FixerInterface::PSR2_LEVEL) + ->fixers(['-yoda_conditions', 'ordered_use', 'short_array_syntax']) + ->finder(Symfony\CS\Finder\DefaultFinder::create() + ->in(__DIR__.'/src/')); \ No newline at end of file diff --git a/vendor/league/flysystem-cached-adapter/.scrutinizer.yml b/vendor/league/flysystem-cached-adapter/.scrutinizer.yml new file mode 100644 index 0000000..fa39b52 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/.scrutinizer.yml @@ -0,0 +1,34 @@ +filter: + paths: [src/*] +checks: + php: + code_rating: true + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true +tools: + external_code_coverage: + timeout: 900 + runs: 6 + php_code_coverage: false + php_code_sniffer: + config: + standard: PSR2 + filter: + paths: ['src'] + php_loc: + enabled: true + excluded_dirs: [vendor, spec, stubs] + php_cpd: + enabled: true + excluded_dirs: [vendor, spec, stubs] \ No newline at end of file diff --git a/vendor/league/flysystem-cached-adapter/.travis.yml b/vendor/league/flysystem-cached-adapter/.travis.yml new file mode 100644 index 0000000..6706449 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/.travis.yml @@ -0,0 +1,29 @@ +language: php + +php: + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + +matrix: + allow_failures: + - php: 5.5 + +env: + - COMPOSER_OPTS="" + - COMPOSER_OPTS="--prefer-lowest" + +install: + - if [[ "${TRAVIS_PHP_VERSION}" == "5.5" ]]; then composer require phpunit/phpunit:^4.8.36 phpspec/phpspec:^2 --prefer-dist --update-with-dependencies; fi + - if [[ "${TRAVIS_PHP_VERSION}" == "7.2" ]]; then composer require phpunit/phpunit:^6.0 --prefer-dist --update-with-dependencies; fi + - travis_retry composer update --prefer-dist $COMPOSER_OPTS + +script: + - vendor/bin/phpspec run + - vendor/bin/phpunit + +after_script: + - wget https://scrutinizer-ci.com/ocular.phar' + - php ocular.phar code-coverage:upload --format=php-clover ./clover/phpunit.xml' diff --git a/vendor/league/flysystem-cached-adapter/LICENSE b/vendor/league/flysystem-cached-adapter/LICENSE new file mode 100644 index 0000000..666f6c8 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Frank de Jonge + +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/league/flysystem-cached-adapter/clover/.gitignore b/vendor/league/flysystem-cached-adapter/clover/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/clover/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/vendor/league/flysystem-cached-adapter/composer.json b/vendor/league/flysystem-cached-adapter/composer.json new file mode 100644 index 0000000..df7fb7f --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/composer.json @@ -0,0 +1,30 @@ +{ + "name": "league/flysystem-cached-adapter", + "description": "An adapter decorator to enable meta-data caching.", + "autoload": { + "psr-4": { + "League\\Flysystem\\Cached\\": "src/" + } + }, + "require": { + "league/flysystem": "~1.0", + "psr/cache": "^1.0.0" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7", + "mockery/mockery": "~0.9", + "predis/predis": "~1.0", + "tedivm/stash": "~0.12" + }, + "suggest": { + "ext-phpredis": "Pure C implemented extension for PHP" + }, + "license": "MIT", + "authors": [ + { + "name": "frankdejonge", + "email": "info@frenky.net" + } + ] +} diff --git a/vendor/league/flysystem-cached-adapter/phpspec.yml b/vendor/league/flysystem-cached-adapter/phpspec.yml new file mode 100644 index 0000000..5eabcb2 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/phpspec.yml @@ -0,0 +1,6 @@ +--- +suites: + cached_adapter_suite: + namespace: League\Flysystem\Cached + psr4_prefix: League\Flysystem\Cached +formatter.name: pretty diff --git a/vendor/league/flysystem-cached-adapter/phpunit.php b/vendor/league/flysystem-cached-adapter/phpunit.php new file mode 100644 index 0000000..d109587 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/phpunit.php @@ -0,0 +1,3 @@ + + + + + ./tests/ + + + + + ./src/ + + + + + + + + diff --git a/vendor/league/flysystem-cached-adapter/readme.md b/vendor/league/flysystem-cached-adapter/readme.md new file mode 100644 index 0000000..dd1433d --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/readme.md @@ -0,0 +1,20 @@ +# Flysystem Cached CachedAdapter + +[![Author](http://img.shields.io/badge/author-@frankdejonge-blue.svg?style=flat-square)](https://twitter.com/frankdejonge) +[![Build Status](https://img.shields.io/travis/thephpleague/flysystem-cached-adapter/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/flysystem-cached-adapter) +[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/flysystem-cached-adapter.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/flysystem-cached-adapter/code-structure) +[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/flysystem-cached-adapter.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/flysystem-cached-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Packagist Version](https://img.shields.io/packagist/v/league/flysystem-cached-adapter.svg?style=flat-square)](https://packagist.org/packages/league/flysystem-cached-adapter) +[![Total Downloads](https://img.shields.io/packagist/dt/league/flysystem-cached-adapter.svg?style=flat-square)](https://packagist.org/packages/league/flysystem-cached-adapter) + + +The adapter decorator caches metadata and directory listings. + +```bash +composer require league/flysystem-cached-adapter +``` + +## Usage + +[Check out the docs.](https://flysystem.thephpleague.com/docs/advanced/caching/) diff --git a/vendor/league/flysystem-cached-adapter/spec/CachedAdapterSpec.php b/vendor/league/flysystem-cached-adapter/spec/CachedAdapterSpec.php new file mode 100644 index 0000000..69428d9 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/spec/CachedAdapterSpec.php @@ -0,0 +1,435 @@ +adapter = $adapter; + $this->cache = $cache; + $this->cache->load()->shouldBeCalled(); + $this->beConstructedWith($adapter, $cache); + } + + public function it_is_initializable() + { + $this->shouldHaveType('League\Flysystem\Cached\CachedAdapter'); + $this->shouldHaveType('League\Flysystem\AdapterInterface'); + } + + public function it_should_forward_read_streams() + { + $path = 'path.txt'; + $response = ['path' => $path]; + $this->adapter->readStream($path)->willReturn($response); + $this->readStream($path)->shouldbe($response); + } + + public function it_should_cache_writes() + { + $type = 'file'; + $path = 'path.txt'; + $contents = 'contents'; + $config = new Config(); + $response = compact('path', 'contents', 'type'); + $this->adapter->write($path, $contents, $config)->willReturn($response); + $this->cache->updateObject($path, $response, true)->shouldBeCalled(); + $this->write($path, $contents, $config)->shouldBe($response); + } + + public function it_should_cache_streamed_writes() + { + $type = 'file'; + $path = 'path.txt'; + $stream = tmpfile(); + $config = new Config(); + $response = compact('path', 'stream', 'type'); + $this->adapter->writeStream($path, $stream, $config)->willReturn($response); + $this->cache->updateObject($path, ['contents' => false] + $response, true)->shouldBeCalled(); + $this->writeStream($path, $stream, $config)->shouldBe($response); + fclose($stream); + } + + public function it_should_cache_streamed_updates() + { + $type = 'file'; + $path = 'path.txt'; + $stream = tmpfile(); + $config = new Config(); + $response = compact('path', 'stream', 'type'); + $this->adapter->updateStream($path, $stream, $config)->willReturn($response); + $this->cache->updateObject($path, ['contents' => false] + $response, true)->shouldBeCalled(); + $this->updateStream($path, $stream, $config)->shouldBe($response); + fclose($stream); + } + + public function it_should_ignore_failed_writes() + { + $path = 'path.txt'; + $contents = 'contents'; + $config = new Config(); + $this->adapter->write($path, $contents, $config)->willReturn(false); + $this->write($path, $contents, $config)->shouldBe(false); + } + + public function it_should_ignore_failed_streamed_writes() + { + $path = 'path.txt'; + $contents = tmpfile(); + $config = new Config(); + $this->adapter->writeStream($path, $contents, $config)->willReturn(false); + $this->writeStream($path, $contents, $config)->shouldBe(false); + fclose($contents); + } + + public function it_should_cache_updated() + { + $type = 'file'; + $path = 'path.txt'; + $contents = 'contents'; + $config = new Config(); + $response = compact('path', 'contents', 'type'); + $this->adapter->update($path, $contents, $config)->willReturn($response); + $this->cache->updateObject($path, $response, true)->shouldBeCalled(); + $this->update($path, $contents, $config)->shouldBe($response); + } + + public function it_should_ignore_failed_updates() + { + $path = 'path.txt'; + $contents = 'contents'; + $config = new Config(); + $this->adapter->update($path, $contents, $config)->willReturn(false); + $this->update($path, $contents, $config)->shouldBe(false); + } + + public function it_should_ignore_failed_streamed_updates() + { + $path = 'path.txt'; + $contents = tmpfile(); + $config = new Config(); + $this->adapter->updateStream($path, $contents, $config)->willReturn(false); + $this->updateStream($path, $contents, $config)->shouldBe(false); + fclose($contents); + } + + public function it_should_cache_renames() + { + $old = 'old.txt'; + $new = 'new.txt'; + $this->adapter->rename($old, $new)->willReturn(true); + $this->cache->rename($old, $new)->shouldBeCalled(); + $this->rename($old, $new)->shouldBe(true); + } + + public function it_should_ignore_rename_fails() + { + $old = 'old.txt'; + $new = 'new.txt'; + $this->adapter->rename($old, $new)->willReturn(false); + $this->rename($old, $new)->shouldBe(false); + } + + public function it_should_cache_copies() + { + $old = 'old.txt'; + $new = 'new.txt'; + $this->adapter->copy($old, $new)->willReturn(true); + $this->cache->copy($old, $new)->shouldBeCalled(); + $this->copy($old, $new)->shouldBe(true); + } + + public function it_should_ignore_copy_fails() + { + $old = 'old.txt'; + $new = 'new.txt'; + $this->adapter->copy($old, $new)->willReturn(false); + $this->copy($old, $new)->shouldBe(false); + } + + public function it_should_cache_deletes() + { + $delete = 'delete.txt'; + $this->adapter->delete($delete)->willReturn(true); + $this->cache->delete($delete)->shouldBeCalled(); + $this->delete($delete)->shouldBe(true); + } + + public function it_should_ignore_delete_fails() + { + $delete = 'delete.txt'; + $this->adapter->delete($delete)->willReturn(false); + $this->delete($delete)->shouldBe(false); + } + + public function it_should_cache_dir_deletes() + { + $delete = 'delete'; + $this->adapter->deleteDir($delete)->willReturn(true); + $this->cache->deleteDir($delete)->shouldBeCalled(); + $this->deleteDir($delete)->shouldBe(true); + } + + public function it_should_ignore_delete_dir_fails() + { + $delete = 'delete'; + $this->adapter->deleteDir($delete)->willReturn(false); + $this->deleteDir($delete)->shouldBe(false); + } + + public function it_should_cache_dir_creates() + { + $dirname = 'dirname'; + $config = new Config(); + $response = ['path' => $dirname, 'type' => 'dir']; + $this->adapter->createDir($dirname, $config)->willReturn($response); + $this->cache->updateObject($dirname, $response, true)->shouldBeCalled(); + $this->createDir($dirname, $config)->shouldBe($response); + } + + public function it_should_ignore_create_dir_fails() + { + $dirname = 'dirname'; + $config = new Config(); + $this->adapter->createDir($dirname, $config)->willReturn(false); + $this->createDir($dirname, $config)->shouldBe(false); + } + + public function it_should_cache_set_visibility() + { + $path = 'path.txt'; + $visibility = AdapterInterface::VISIBILITY_PUBLIC; + $this->adapter->setVisibility($path, $visibility)->willReturn(true); + $this->cache->updateObject($path, ['path' => $path, 'visibility' => $visibility], true)->shouldBeCalled(); + $this->setVisibility($path, $visibility)->shouldBe(true); + } + + public function it_should_ignore_set_visibility_fails() + { + $dirname = 'delete'; + $visibility = AdapterInterface::VISIBILITY_PUBLIC; + $this->adapter->setVisibility($dirname, $visibility)->willReturn(false); + $this->setVisibility($dirname, $visibility)->shouldBe(false); + } + + public function it_should_indicate_missing_files() + { + $this->cache->has($path = 'path.txt')->willReturn(false); + $this->has($path)->shouldBe(false); + } + + public function it_should_indicate_file_existance() + { + $this->cache->has($path = 'path.txt')->willReturn(true); + $this->has($path)->shouldBe(true); + } + + public function it_should_cache_missing_files() + { + $this->cache->has($path = 'path.txt')->willReturn(null); + $this->adapter->has($path)->willReturn(false); + $this->cache->storeMiss($path)->shouldBeCalled(); + $this->has($path)->shouldBe(false); + } + + public function it_should_delete_when_metadata_is_missing() + { + $path = 'path.txt'; + $this->cache->has($path)->willReturn(true); + $this->cache->getSize($path)->willReturn(['path' => $path]); + $this->adapter->getSize($path)->willReturn($response = ['path' => $path, 'size' => 1024]); + $this->cache->updateObject($path, $response, true)->shouldBeCalled(); + $this->getSize($path)->shouldBe($response); + } + + public function it_should_cache_has() + { + $this->cache->has($path = 'path.txt')->willReturn(null); + $this->adapter->has($path)->willReturn(true); + $this->cache->updateObject($path, compact('path'), true)->shouldBeCalled(); + $this->has($path)->shouldBe(true); + } + + public function it_should_list_cached_contents() + { + $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(true); + $response = [['path' => 'path.txt']]; + $this->cache->listContents($dirname, $recursive)->willReturn($response); + $this->listContents($dirname, $recursive)->shouldBe($response); + } + + public function it_should_ignore_failed_list_contents() + { + $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(false); + $this->adapter->listContents($dirname, $recursive)->willReturn(false); + $this->listContents($dirname, $recursive)->shouldBe(false); + } + + public function it_should_cache_contents_listings() + { + $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(false); + $response = [['path' => 'path.txt']]; + $this->adapter->listContents($dirname, $recursive)->willReturn($response); + $this->cache->storeContents($dirname, $response, $recursive)->shouldBeCalled(); + $this->listContents($dirname, $recursive)->shouldBe($response); + } + + public function it_should_use_cached_visibility() + { + $this->make_it_use_getter_cache('getVisibility', 'path.txt', [ + 'path' => 'path.txt', + 'visibility' => AdapterInterface::VISIBILITY_PUBLIC, + ]); + } + + public function it_should_cache_get_visibility() + { + $path = 'path.txt'; + $response = ['visibility' => AdapterInterface::VISIBILITY_PUBLIC, 'path' => $path]; + $this->make_it_cache_getter('getVisibility', $path, $response); + } + + public function it_should_ignore_failed_get_visibility() + { + $path = 'path.txt'; + $this->make_it_ignore_failed_getter('getVisibility', $path); + } + + public function it_should_use_cached_timestamp() + { + $this->make_it_use_getter_cache('getTimestamp', 'path.txt', [ + 'path' => 'path.txt', + 'timestamp' => 1234, + ]); + } + + public function it_should_cache_timestamps() + { + $this->make_it_cache_getter('getTimestamp', 'path.txt', [ + 'path' => 'path.txt', + 'timestamp' => 1234, + ]); + } + + public function it_should_ignore_failed_get_timestamps() + { + $this->make_it_ignore_failed_getter('getTimestamp', 'path.txt'); + } + + public function it_should_cache_get_metadata() + { + $path = 'path.txt'; + $response = ['visibility' => AdapterInterface::VISIBILITY_PUBLIC, 'path' => $path]; + $this->make_it_cache_getter('getMetadata', $path, $response); + } + + public function it_should_use_cached_metadata() + { + $this->make_it_use_getter_cache('getMetadata', 'path.txt', [ + 'path' => 'path.txt', + 'timestamp' => 1234, + ]); + } + + public function it_should_ignore_failed_get_metadata() + { + $this->make_it_ignore_failed_getter('getMetadata', 'path.txt'); + } + + public function it_should_cache_get_size() + { + $path = 'path.txt'; + $response = ['size' => 1234, 'path' => $path]; + $this->make_it_cache_getter('getSize', $path, $response); + } + + public function it_should_use_cached_size() + { + $this->make_it_use_getter_cache('getSize', 'path.txt', [ + 'path' => 'path.txt', + 'size' => 1234, + ]); + } + + public function it_should_ignore_failed_get_size() + { + $this->make_it_ignore_failed_getter('getSize', 'path.txt'); + } + + public function it_should_cache_get_mimetype() + { + $path = 'path.txt'; + $response = ['mimetype' => 'text/plain', 'path' => $path]; + $this->make_it_cache_getter('getMimetype', $path, $response); + } + + public function it_should_use_cached_mimetype() + { + $this->make_it_use_getter_cache('getMimetype', 'path.txt', [ + 'path' => 'path.txt', + 'mimetype' => 'text/plain', + ]); + } + + public function it_should_ignore_failed_get_mimetype() + { + $this->make_it_ignore_failed_getter('getMimetype', 'path.txt'); + } + + public function it_should_cache_reads() + { + $path = 'path.txt'; + $response = ['path' => $path, 'contents' => 'contents']; + $this->make_it_cache_getter('read', $path, $response); + } + + public function it_should_use_cached_file_contents() + { + $this->make_it_use_getter_cache('read', 'path.txt', [ + 'path' => 'path.txt', + 'contents' => 'contents' + ]); + } + + public function it_should_ignore_failed_reads() + { + $this->make_it_ignore_failed_getter('read', 'path.txt'); + } + + protected function make_it_use_getter_cache($method, $path, $response) + { + $this->cache->{$method}($path)->willReturn($response); + $this->{$method}($path)->shouldBe($response); + } + + protected function make_it_cache_getter($method, $path, $response) + { + $this->cache->{$method}($path)->willReturn(false); + $this->adapter->{$method}($path)->willReturn($response); + $this->cache->updateObject($path, $response, true)->shouldBeCalled(); + $this->{$method}($path)->shouldBe($response); + } + + protected function make_it_ignore_failed_getter($method, $path) + { + $this->cache->{$method}($path)->willReturn(false); + $this->adapter->{$method}($path)->willReturn(false); + $this->{$method}($path)->shouldBe(false); + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/CacheInterface.php b/vendor/league/flysystem-cached-adapter/src/CacheInterface.php new file mode 100644 index 0000000..de3ab3d --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/CacheInterface.php @@ -0,0 +1,101 @@ +adapter = $adapter; + $this->cache = $cache; + $this->cache->load(); + } + + /** + * Get the underlying Adapter implementation. + * + * @return AdapterInterface + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Get the used Cache implementation. + * + * @return CacheInterface + */ + public function getCache() + { + return $this->cache; + } + + /** + * {@inheritdoc} + */ + public function write($path, $contents, Config $config) + { + $result = $this->adapter->write($path, $contents, $config); + + if ($result !== false) { + $result['type'] = 'file'; + $this->cache->updateObject($path, $result + compact('path', 'contents'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function writeStream($path, $resource, Config $config) + { + $result = $this->adapter->writeStream($path, $resource, $config); + + if ($result !== false) { + $result['type'] = 'file'; + $contents = false; + $this->cache->updateObject($path, $result + compact('path', 'contents'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function update($path, $contents, Config $config) + { + $result = $this->adapter->update($path, $contents, $config); + + if ($result !== false) { + $result['type'] = 'file'; + $this->cache->updateObject($path, $result + compact('path', 'contents'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function updateStream($path, $resource, Config $config) + { + $result = $this->adapter->updateStream($path, $resource, $config); + + if ($result !== false) { + $result['type'] = 'file'; + $contents = false; + $this->cache->updateObject($path, $result + compact('path', 'contents'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function rename($path, $newPath) + { + $result = $this->adapter->rename($path, $newPath); + + if ($result !== false) { + $this->cache->rename($path, $newPath); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function copy($path, $newpath) + { + $result = $this->adapter->copy($path, $newpath); + + if ($result !== false) { + $this->cache->copy($path, $newpath); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function delete($path) + { + $result = $this->adapter->delete($path); + + if ($result !== false) { + $this->cache->delete($path); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function deleteDir($dirname) + { + $result = $this->adapter->deleteDir($dirname); + + if ($result !== false) { + $this->cache->deleteDir($dirname); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function createDir($dirname, Config $config) + { + $result = $this->adapter->createDir($dirname, $config); + + if ($result !== false) { + $type = 'dir'; + $path = $dirname; + $this->cache->updateObject($dirname, compact('path', 'type'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function setVisibility($path, $visibility) + { + $result = $this->adapter->setVisibility($path, $visibility); + + if ($result !== false) { + $this->cache->updateObject($path, compact('path', 'visibility'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function has($path) + { + $cacheHas = $this->cache->has($path); + + if ($cacheHas !== null) { + return $cacheHas; + } + + $adapterResponse = $this->adapter->has($path); + + if (! $adapterResponse) { + $this->cache->storeMiss($path); + } else { + $cacheEntry = is_array($adapterResponse) ? $adapterResponse : compact('path'); + $this->cache->updateObject($path, $cacheEntry, true); + } + + return $adapterResponse; + } + + /** + * {@inheritdoc} + */ + public function read($path) + { + return $this->callWithFallback('contents', $path, 'read'); + } + + /** + * {@inheritdoc} + */ + public function readStream($path) + { + return $this->adapter->readStream($path); + } + + /** + * {@inheritdoc} + */ + public function listContents($directory = '', $recursive = false) + { + if ($this->cache->isComplete($directory, $recursive)) { + return $this->cache->listContents($directory, $recursive); + } + + $result = $this->adapter->listContents($directory, $recursive); + + if ($result !== false) { + $this->cache->storeContents($directory, $result, $recursive); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getMetadata($path) + { + return $this->callWithFallback(null, $path, 'getMetadata'); + } + + /** + * {@inheritdoc} + */ + public function getSize($path) + { + return $this->callWithFallback('size', $path, 'getSize'); + } + + /** + * {@inheritdoc} + */ + public function getMimetype($path) + { + return $this->callWithFallback('mimetype', $path, 'getMimetype'); + } + + /** + * {@inheritdoc} + */ + public function getTimestamp($path) + { + return $this->callWithFallback('timestamp', $path, 'getTimestamp'); + } + + /** + * {@inheritdoc} + */ + public function getVisibility($path) + { + return $this->callWithFallback('visibility', $path, 'getVisibility'); + } + + /** + * Call a method and cache the response. + * + * @param string $property + * @param string $path + * @param string $method + * + * @return mixed + */ + protected function callWithFallback($property, $path, $method) + { + $result = $this->cache->{$method}($path); + + if ($result !== false && ($property === null || array_key_exists($property, $result))) { + return $result; + } + + $result = $this->adapter->{$method}($path); + + if ($result) { + $object = $result + compact('path'); + $this->cache->updateObject($path, $object, true); + } + + return $result; + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php b/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php new file mode 100644 index 0000000..c2076d4 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php @@ -0,0 +1,417 @@ +autosave) { + $this->save(); + } + } + + /** + * Get the autosave setting. + * + * @return bool autosave + */ + public function getAutosave() + { + return $this->autosave; + } + + /** + * Get the autosave setting. + * + * @param bool $autosave + */ + public function setAutosave($autosave) + { + $this->autosave = $autosave; + } + + /** + * Store the contents listing. + * + * @param string $directory + * @param array $contents + * @param bool $recursive + * + * @return array contents listing + */ + public function storeContents($directory, array $contents, $recursive = false) + { + $directories = [$directory]; + + foreach ($contents as $object) { + $this->updateObject($object['path'], $object); + $object = $this->cache[$object['path']]; + + if ($recursive && $this->pathIsInDirectory($directory, $object['path'])) { + $directories[] = $object['dirname']; + } + } + + foreach (array_unique($directories) as $directory) { + $this->setComplete($directory, $recursive); + } + + $this->autosave(); + } + + /** + * Update the metadata for an object. + * + * @param string $path object path + * @param array $object object metadata + * @param bool $autosave whether to trigger the autosave routine + */ + public function updateObject($path, array $object, $autosave = false) + { + if (! $this->has($path)) { + $this->cache[$path] = Util::pathinfo($path); + } + + $this->cache[$path] = array_merge($this->cache[$path], $object); + + if ($autosave) { + $this->autosave(); + } + + $this->ensureParentDirectories($path); + } + + /** + * Store object hit miss. + * + * @param string $path + */ + public function storeMiss($path) + { + $this->cache[$path] = false; + $this->autosave(); + } + + /** + * Get the contents listing. + * + * @param string $dirname + * @param bool $recursive + * + * @return array contents listing + */ + public function listContents($dirname = '', $recursive = false) + { + $result = []; + + foreach ($this->cache as $object) { + if ($object === false) { + continue; + } + if ($object['dirname'] === $dirname) { + $result[] = $object; + } elseif ($recursive && $this->pathIsInDirectory($dirname, $object['path'])) { + $result[] = $object; + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function has($path) + { + if ($path !== false && array_key_exists($path, $this->cache)) { + return $this->cache[$path] !== false; + } + + if ($this->isComplete(Util::dirname($path), false)) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function read($path) + { + if (isset($this->cache[$path]['contents']) && $this->cache[$path]['contents'] !== false) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function readStream($path) + { + return false; + } + + /** + * {@inheritdoc} + */ + public function rename($path, $newpath) + { + if ($this->has($path)) { + $object = $this->cache[$path]; + unset($this->cache[$path]); + $object['path'] = $newpath; + $object = array_merge($object, Util::pathinfo($newpath)); + $this->cache[$newpath] = $object; + $this->autosave(); + } + } + + /** + * {@inheritdoc} + */ + public function copy($path, $newpath) + { + if ($this->has($path)) { + $object = $this->cache[$path]; + $object = array_merge($object, Util::pathinfo($newpath)); + $this->updateObject($newpath, $object, true); + } + } + + /** + * {@inheritdoc} + */ + public function delete($path) + { + $this->storeMiss($path); + } + + /** + * {@inheritdoc} + */ + public function deleteDir($dirname) + { + foreach ($this->cache as $path => $object) { + if ($this->pathIsInDirectory($dirname, $path) || $path === $dirname) { + unset($this->cache[$path]); + } + } + + unset($this->complete[$dirname]); + + $this->autosave(); + } + + /** + * {@inheritdoc} + */ + public function getMimetype($path) + { + if (isset($this->cache[$path]['mimetype'])) { + return $this->cache[$path]; + } + + if (! $result = $this->read($path)) { + return false; + } + + $mimetype = Util::guessMimeType($path, $result['contents']); + $this->cache[$path]['mimetype'] = $mimetype; + + return $this->cache[$path]; + } + + /** + * {@inheritdoc} + */ + public function getSize($path) + { + if (isset($this->cache[$path]['size'])) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getTimestamp($path) + { + if (isset($this->cache[$path]['timestamp'])) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getVisibility($path) + { + if (isset($this->cache[$path]['visibility'])) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getMetadata($path) + { + if (isset($this->cache[$path]['type'])) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function isComplete($dirname, $recursive) + { + if (! array_key_exists($dirname, $this->complete)) { + return false; + } + + if ($recursive && $this->complete[$dirname] !== 'recursive') { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function setComplete($dirname, $recursive) + { + $this->complete[$dirname] = $recursive ? 'recursive' : true; + } + + /** + * Filter the contents from a listing. + * + * @param array $contents object listing + * + * @return array filtered contents + */ + public function cleanContents(array $contents) + { + $cachedProperties = array_flip([ + 'path', 'dirname', 'basename', 'extension', 'filename', + 'size', 'mimetype', 'visibility', 'timestamp', 'type', + ]); + + foreach ($contents as $path => $object) { + if (is_array($object)) { + $contents[$path] = array_intersect_key($object, $cachedProperties); + } + } + + return $contents; + } + + /** + * {@inheritdoc} + */ + public function flush() + { + $this->cache = []; + $this->complete = []; + $this->autosave(); + } + + /** + * {@inheritdoc} + */ + public function autosave() + { + if ($this->autosave) { + $this->save(); + } + } + + /** + * Retrieve serialized cache data. + * + * @return string serialized data + */ + public function getForStorage() + { + $cleaned = $this->cleanContents($this->cache); + + return json_encode([$cleaned, $this->complete]); + } + + /** + * Load from serialized cache data. + * + * @param string $json + */ + public function setFromStorage($json) + { + list($cache, $complete) = json_decode($json, true); + + if (json_last_error() === JSON_ERROR_NONE && is_array($cache) && is_array($complete)) { + $this->cache = $cache; + $this->complete = $complete; + } + } + + /** + * Ensure parent directories of an object. + * + * @param string $path object path + */ + public function ensureParentDirectories($path) + { + $object = $this->cache[$path]; + + while ($object['dirname'] !== '' && ! isset($this->cache[$object['dirname']])) { + $object = Util::pathinfo($object['dirname']); + $object['type'] = 'dir'; + $this->cache[$object['path']] = $object; + } + } + + /** + * Determines if the path is inside the directory. + * + * @param string $directory + * @param string $path + * + * @return bool + */ + protected function pathIsInDirectory($directory, $path) + { + return $directory === '' || strpos($path, $directory . '/') === 0; + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Adapter.php b/vendor/league/flysystem-cached-adapter/src/Storage/Adapter.php new file mode 100644 index 0000000..3aa8b1a --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Adapter.php @@ -0,0 +1,115 @@ +adapter = $adapter; + $this->file = $file; + $this->setExpire($expire); + } + + /** + * Set the expiration time in seconds. + * + * @param int $expire relative expiration time + */ + protected function setExpire($expire) + { + if ($expire) { + $this->expire = $this->getTime($expire); + } + } + + /** + * Get expiration time in seconds. + * + * @param int $time relative expiration time + * + * @return int actual expiration time + */ + protected function getTime($time = 0) + { + return intval(microtime(true)) + $time; + } + + /** + * {@inheritdoc} + */ + public function setFromStorage($json) + { + list($cache, $complete, $expire) = json_decode($json, true); + + if (! $expire || $expire > $this->getTime()) { + $this->cache = $cache; + $this->complete = $complete; + } else { + $this->adapter->delete($this->file); + } + } + + /** + * {@inheritdoc} + */ + public function load() + { + if ($this->adapter->has($this->file)) { + $file = $this->adapter->read($this->file); + if ($file && !empty($file['contents'])) { + $this->setFromStorage($file['contents']); + } + } + } + + /** + * {@inheritdoc} + */ + public function getForStorage() + { + $cleaned = $this->cleanContents($this->cache); + + return json_encode([$cleaned, $this->complete, $this->expire]); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $config = new Config(); + $contents = $this->getForStorage(); + + if ($this->adapter->has($this->file)) { + $this->adapter->update($this->file, $contents, $config); + } else { + $this->adapter->write($this->file, $contents, $config); + } + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Memcached.php b/vendor/league/flysystem-cached-adapter/src/Storage/Memcached.php new file mode 100644 index 0000000..f67d271 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Memcached.php @@ -0,0 +1,59 @@ +key = $key; + $this->expire = $expire; + $this->memcached = $memcached; + } + + /** + * {@inheritdoc} + */ + public function load() + { + $contents = $this->memcached->get($this->key); + + if ($contents !== false) { + $this->setFromStorage($contents); + } + } + + /** + * {@inheritdoc} + */ + public function save() + { + $contents = $this->getForStorage(); + $expiration = $this->expire === null ? 0 : time() + $this->expire; + $this->memcached->set($this->key, $contents, $expiration); + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Memory.php b/vendor/league/flysystem-cached-adapter/src/Storage/Memory.php new file mode 100644 index 0000000..d0914fa --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Memory.php @@ -0,0 +1,22 @@ +client = $client ?: new Redis(); + $this->key = $key; + $this->expire = $expire; + } + + /** + * {@inheritdoc} + */ + public function load() + { + $contents = $this->client->get($this->key); + + if ($contents !== false) { + $this->setFromStorage($contents); + } + } + + /** + * {@inheritdoc} + */ + public function save() + { + $contents = $this->getForStorage(); + $this->client->set($this->key, $contents); + + if ($this->expire !== null) { + $this->client->expire($this->key, $this->expire); + } + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Predis.php b/vendor/league/flysystem-cached-adapter/src/Storage/Predis.php new file mode 100644 index 0000000..8a29574 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Predis.php @@ -0,0 +1,75 @@ +client = $client ?: new Client(); + $this->key = $key; + $this->expire = $expire; + } + + /** + * {@inheritdoc} + */ + public function load() + { + if (($contents = $this->executeCommand('get', [$this->key])) !== null) { + $this->setFromStorage($contents); + } + } + + /** + * {@inheritdoc} + */ + public function save() + { + $contents = $this->getForStorage(); + $this->executeCommand('set', [$this->key, $contents]); + + if ($this->expire !== null) { + $this->executeCommand('expire', [$this->key, $this->expire]); + } + } + + /** + * Execute a Predis command. + * + * @param string $name + * @param array $arguments + * + * @return string + */ + protected function executeCommand($name, array $arguments) + { + $command = $this->client->createCommand($name, $arguments); + + return $this->client->executeCommand($command); + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php b/vendor/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php new file mode 100644 index 0000000..43be87e --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php @@ -0,0 +1,59 @@ +pool = $pool; + $this->key = $key; + $this->expire = $expire; + } + + /** + * {@inheritdoc} + */ + public function save() + { + $item = $this->pool->getItem($this->key); + $item->set($this->getForStorage()); + $item->expiresAfter($this->expire); + $this->pool->save($item); + } + + /** + * {@inheritdoc} + */ + public function load() + { + $item = $this->pool->getItem($this->key); + if ($item->isHit()) { + $this->setFromStorage($item->get()); + } + } +} \ No newline at end of file diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Stash.php b/vendor/league/flysystem-cached-adapter/src/Storage/Stash.php new file mode 100644 index 0000000..e05b832 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Stash.php @@ -0,0 +1,60 @@ +key = $key; + $this->expire = $expire; + $this->pool = $pool; + } + + /** + * {@inheritdoc} + */ + public function load() + { + $item = $this->pool->getItem($this->key); + $contents = $item->get(); + + if ($item->isMiss() === false) { + $this->setFromStorage($contents); + } + } + + /** + * {@inheritdoc} + */ + public function save() + { + $contents = $this->getForStorage(); + $item = $this->pool->getItem($this->key); + $item->set($contents, $this->expire); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/AdapterCacheTests.php b/vendor/league/flysystem-cached-adapter/tests/AdapterCacheTests.php new file mode 100644 index 0000000..b63cba7 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/AdapterCacheTests.php @@ -0,0 +1,104 @@ +shouldReceive('has')->once()->with('file.json')->andReturn(false); + $cache = new Adapter($adapter, 'file.json', 10); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadExpired() + { + $response = ['contents' => json_encode([[], ['' => true], 1234567890]), 'path' => 'file.json']; + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(true); + $adapter->shouldReceive('read')->once()->with('file.json')->andReturn($response); + $adapter->shouldReceive('delete')->once()->with('file.json'); + $cache = new Adapter($adapter, 'file.json', 10); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = ['contents' => json_encode([[], ['' => true], 9876543210]), 'path' => 'file.json']; + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(true); + $adapter->shouldReceive('read')->once()->with('file.json')->andReturn($response); + $cache = new Adapter($adapter, 'file.json', 10); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSaveExists() + { + $response = json_encode([[], [], null]); + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(true); + $adapter->shouldReceive('update')->once()->with('file.json', $response, Mockery::any()); + $cache = new Adapter($adapter, 'file.json', null); + $cache->save(); + } + + public function testSaveNew() + { + $response = json_encode([[], [], null]); + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(false); + $adapter->shouldReceive('write')->once()->with('file.json', $response, Mockery::any()); + $cache = new Adapter($adapter, 'file.json', null); + $cache->save(); + } + + public function testStoreContentsRecursive() + { + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(false); + $adapter->shouldReceive('write')->once()->with('file.json', Mockery::any(), Mockery::any()); + + $cache = new Adapter($adapter, 'file.json', null); + + $contents = [ + ['path' => 'foo/bar', 'dirname' => 'foo'], + ['path' => 'afoo/bang', 'dirname' => 'afoo'], + ]; + + $cache->storeContents('foo', $contents, true); + + $this->assertTrue($cache->isComplete('foo', true)); + $this->assertFalse($cache->isComplete('afoo', true)); + } + + public function testDeleteDir() + { + $cache_data = [ + 'foo' => ['path' => 'foo', 'type' => 'dir', 'dirname' => ''], + 'foo/bar' => ['path' => 'foo/bar', 'type' => 'file', 'dirname' => 'foo'], + 'foobaz' => ['path' => 'foobaz', 'type' => 'file', 'dirname' => ''], + ]; + + $response = [ + 'contents' => json_encode([$cache_data, [], null]), + 'path' => 'file.json', + ]; + + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->zeroOrMoreTimes()->with('file.json')->andReturn(true); + $adapter->shouldReceive('read')->once()->with('file.json')->andReturn($response); + $adapter->shouldReceive('update')->once()->with('file.json', Mockery::any(), Mockery::any())->andReturn(true); + + $cache = new Adapter($adapter, 'file.json', null); + $cache->load(); + + $cache->deleteDir('foo', true); + + $this->assertSame(1, count($cache->listContents('', true))); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/InspectionTests.php b/vendor/league/flysystem-cached-adapter/tests/InspectionTests.php new file mode 100644 index 0000000..40d4c91 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/InspectionTests.php @@ -0,0 +1,16 @@ +shouldReceive('load')->once(); + $cached_adapter = new CachedAdapter($adapter, $cache); + $this->assertInstanceOf('League\Flysystem\AdapterInterface', $cached_adapter->getAdapter()); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/MemcachedTests.php b/vendor/league/flysystem-cached-adapter/tests/MemcachedTests.php new file mode 100644 index 0000000..e3d9ad9 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/MemcachedTests.php @@ -0,0 +1,35 @@ +shouldReceive('get')->once()->andReturn(false); + $cache = new Memcached($client); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $client = Mockery::mock('Memcached'); + $client->shouldReceive('get')->once()->andReturn($response); + $cache = new Memcached($client); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $response = json_encode([[], []]); + $client = Mockery::mock('Memcached'); + $client->shouldReceive('set')->once()->andReturn($response); + $cache = new Memcached($client); + $cache->save(); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/MemoryCacheTests.php b/vendor/league/flysystem-cached-adapter/tests/MemoryCacheTests.php new file mode 100644 index 0000000..3ac58fd --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/MemoryCacheTests.php @@ -0,0 +1,255 @@ +setAutosave(true); + $this->assertTrue($cache->getAutosave()); + $cache->setAutosave(false); + $this->assertFalse($cache->getAutosave()); + } + + public function testCacheMiss() + { + $cache = new Memory(); + $cache->storeMiss('path.txt'); + $this->assertFalse($cache->has('path.txt')); + } + + public function testIsComplete() + { + $cache = new Memory(); + $this->assertFalse($cache->isComplete('dirname', false)); + $cache->setComplete('dirname', false); + $this->assertFalse($cache->isComplete('dirname', true)); + $cache->setComplete('dirname', true); + $this->assertTrue($cache->isComplete('dirname', true)); + } + + public function testCleanContents() + { + $cache = new Memory(); + $input = [[ + 'path' => 'path.txt', + 'visibility' => 'public', + 'invalid' => 'thing', + ]]; + + $expected = [[ + 'path' => 'path.txt', + 'visibility' => 'public', + ]]; + + $output = $cache->cleanContents($input); + $this->assertEquals($expected, $output); + } + + public function testGetForStorage() + { + $cache = new Memory(); + $input = [[ + 'path' => 'path.txt', + 'visibility' => 'public', + 'type' => 'file', + ]]; + + $cache->storeContents('', $input, true); + $contents = $cache->listContents('', true); + $cached = []; + foreach ($contents as $item) { + $cached[$item['path']] = $item; + } + + $this->assertEquals(json_encode([$cached, ['' => 'recursive']]), $cache->getForStorage()); + } + + public function testParentCompleteIsUsedDuringHas() + { + $cache = new Memory(); + $cache->setComplete('dirname', false); + $this->assertFalse($cache->has('dirname/path.txt')); + } + + public function testFlush() + { + $cache = new Memory(); + $cache->setComplete('dirname', true); + $cache->updateObject('path.txt', [ + 'path' => 'path.txt', + 'visibility' => 'public', + ]); + $cache->flush(); + $this->assertFalse($cache->isComplete('dirname', true)); + $this->assertNull($cache->has('path.txt')); + } + + public function testSetFromStorage() + { + $cache = new Memory(); + $json = [[ + 'path.txt' => ['path' => 'path.txt', 'type' => 'file'], + ], ['dirname' => 'recursive']]; + $jsonString = json_encode($json); + $cache->setFromStorage($jsonString); + $this->assertTrue($cache->has('path.txt')); + $this->assertTrue($cache->isComplete('dirname', true)); + } + + public function testGetMetadataFail() + { + $cache = new Memory(); + $this->assertFalse($cache->getMetadata('path.txt')); + } + + public function metaGetterProvider() + { + return [ + ['getTimestamp', 'timestamp', 12344], + ['getMimetype', 'mimetype', 'text/plain'], + ['getSize', 'size', 12], + ['getVisibility', 'visibility', 'private'], + ['read', 'contents', '__contents__'], + ]; + } + + /** + * @dataProvider metaGetterProvider + * + * @param $method + * @param $key + * @param $value + */ + public function testMetaGetters($method, $key, $value) + { + $cache = new Memory(); + $this->assertFalse($cache->{$method}('path.txt')); + $cache->updateObject('path.txt', $object = [ + 'path' => 'path.txt', + 'type' => 'file', + $key => $value, + ] + Util::pathinfo('path.txt'), true); + $this->assertEquals($object, $cache->{$method}('path.txt')); + $this->assertEquals($object, $cache->getMetadata('path.txt')); + } + + public function testGetDerivedMimetype() + { + $cache = new Memory(); + $cache->updateObject('path.txt', [ + 'contents' => 'something', + ]); + $response = $cache->getMimetype('path.txt'); + $this->assertEquals('text/plain', $response['mimetype']); + } + + public function testCopyFail() + { + $cache = new Memory(); + $cache->copy('one', 'two'); + $this->assertNull($cache->has('two')); + $this->assertNull($cache->load()); + } + + public function testStoreContents() + { + $cache = new Memory(); + $cache->storeContents('dirname', [ + ['path' => 'dirname', 'type' => 'dir'], + ['path' => 'dirname/nested', 'type' => 'dir'], + ['path' => 'dirname/nested/deep', 'type' => 'dir'], + ['path' => 'other/nested/deep', 'type' => 'dir'], + ], true); + + $this->isTrue($cache->isComplete('other/nested', true)); + } + + public function testDelete() + { + $cache = new Memory(); + $cache->updateObject('path.txt', ['type' => 'file']); + $this->assertTrue($cache->has('path.txt')); + $cache->delete('path.txt'); + $this->assertFalse($cache->has('path.txt')); + } + + public function testDeleteDir() + { + $cache = new Memory(); + $cache->storeContents('dirname', [ + ['path' => 'dirname/path.txt', 'type' => 'file'], + ]); + $this->assertTrue($cache->isComplete('dirname', false)); + $this->assertTrue($cache->has('dirname/path.txt')); + $cache->deleteDir('dirname'); + $this->assertFalse($cache->isComplete('dirname', false)); + $this->assertNull($cache->has('dirname/path.txt')); + } + + public function testReadStream() + { + $cache = new Memory(); + $this->assertFalse($cache->readStream('path.txt')); + } + + public function testRename() + { + $cache = new Memory(); + $cache->updateObject('path.txt', ['type' => 'file']); + $cache->rename('path.txt', 'newpath.txt'); + $this->assertTrue($cache->has('newpath.txt')); + } + + public function testCopy() + { + $cache = new Memory(); + $cache->updateObject('path.txt', ['type' => 'file']); + $cache->copy('path.txt', 'newpath.txt'); + $this->assertTrue($cache->has('newpath.txt')); + } + + public function testComplextListContents() + { + $cache = new Memory(); + $cache->storeContents('', [ + ['path' => 'dirname', 'type' => 'dir'], + ['path' => 'dirname/file.txt', 'type' => 'file'], + ['path' => 'other', 'type' => 'dir'], + ['path' => 'other/file.txt', 'type' => 'file'], + ['path' => 'other/nested/file.txt', 'type' => 'file'], + ]); + + $this->assertCount(3, $cache->listContents('other', true)); + } + + public function testComplextListContentsWithDeletedFile() + { + $cache = new Memory(); + $cache->storeContents('', [ + ['path' => 'dirname', 'type' => 'dir'], + ['path' => 'dirname/file.txt', 'type' => 'file'], + ['path' => 'other', 'type' => 'dir'], + ['path' => 'other/file.txt', 'type' => 'file'], + ['path' => 'other/another_file.txt', 'type' => 'file'], + ]); + + $cache->delete('other/another_file.txt'); + $this->assertCount(4, $cache->listContents('', true)); + } + + public function testCacheMissIfContentsIsFalse() + { + $cache = new Memory(); + $cache->updateObject('path.txt', [ + 'path' => 'path.txt', + 'contents' => false, + ], true); + + $this->assertFalse($cache->read('path.txt')); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/NoopCacheTests.php b/vendor/league/flysystem-cached-adapter/tests/NoopCacheTests.php new file mode 100644 index 0000000..148616f --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/NoopCacheTests.php @@ -0,0 +1,35 @@ +assertEquals($cache, $cache->storeMiss('file.txt')); + $this->assertNull($cache->setComplete('', false)); + $this->assertNull($cache->load()); + $this->assertNull($cache->flush()); + $this->assertNull($cache->has('path.txt')); + $this->assertNull($cache->autosave()); + $this->assertFalse($cache->isComplete('', false)); + $this->assertFalse($cache->read('something')); + $this->assertFalse($cache->readStream('something')); + $this->assertFalse($cache->getMetadata('something')); + $this->assertFalse($cache->getMimetype('something')); + $this->assertFalse($cache->getSize('something')); + $this->assertFalse($cache->getTimestamp('something')); + $this->assertFalse($cache->getVisibility('something')); + $this->assertEmpty($cache->listContents('', false)); + $this->assertFalse($cache->rename('', '')); + $this->assertFalse($cache->copy('', '')); + $this->assertNull($cache->save()); + $object = ['path' => 'path.ext']; + $this->assertEquals($object, $cache->updateObject('path.txt', $object)); + $this->assertEquals([['path' => 'some/file.txt']], $cache->storeContents('unknwon', [ + ['path' => 'some/file.txt'], + ], false)); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/PhpRedisTests.php b/vendor/league/flysystem-cached-adapter/tests/PhpRedisTests.php new file mode 100644 index 0000000..d1ccb65 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/PhpRedisTests.php @@ -0,0 +1,45 @@ +shouldReceive('get')->with('flysystem')->once()->andReturn(false); + $cache = new PhpRedis($client); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $client = Mockery::mock('Redis'); + $client->shouldReceive('get')->with('flysystem')->once()->andReturn($response); + $cache = new PhpRedis($client); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $data = json_encode([[], []]); + $client = Mockery::mock('Redis'); + $client->shouldReceive('set')->with('flysystem', $data)->once(); + $cache = new PhpRedis($client); + $cache->save(); + } + + public function testSaveWithExpire() + { + $data = json_encode([[], []]); + $client = Mockery::mock('Redis'); + $client->shouldReceive('set')->with('flysystem', $data)->once(); + $client->shouldReceive('expire')->with('flysystem', 20)->once(); + $cache = new PhpRedis($client, 'flysystem', 20); + $cache->save(); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/PredisTests.php b/vendor/league/flysystem-cached-adapter/tests/PredisTests.php new file mode 100644 index 0000000..e33e104 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/PredisTests.php @@ -0,0 +1,55 @@ +shouldReceive('createCommand')->with('get', ['flysystem'])->once()->andReturn($command); + $client->shouldReceive('executeCommand')->with($command)->andReturn(null); + $cache = new Predis($client); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $client = Mockery::mock('Predis\Client'); + $command = Mockery::mock('Predis\Command\CommandInterface'); + $client->shouldReceive('createCommand')->with('get', ['flysystem'])->once()->andReturn($command); + $client->shouldReceive('executeCommand')->with($command)->andReturn($response); + $cache = new Predis($client); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $data = json_encode([[], []]); + $client = Mockery::mock('Predis\Client'); + $command = Mockery::mock('Predis\Command\CommandInterface'); + $client->shouldReceive('createCommand')->with('set', ['flysystem', $data])->once()->andReturn($command); + $client->shouldReceive('executeCommand')->with($command)->once(); + $cache = new Predis($client); + $cache->save(); + } + + public function testSaveWithExpire() + { + $data = json_encode([[], []]); + $client = Mockery::mock('Predis\Client'); + $command = Mockery::mock('Predis\Command\CommandInterface'); + $client->shouldReceive('createCommand')->with('set', ['flysystem', $data])->once()->andReturn($command); + $client->shouldReceive('executeCommand')->with($command)->once(); + $expireCommand = Mockery::mock('Predis\Command\CommandInterface'); + $client->shouldReceive('createCommand')->with('expire', ['flysystem', 20])->once()->andReturn($expireCommand); + $client->shouldReceive('executeCommand')->with($expireCommand)->once(); + $cache = new Predis($client, 'flysystem', 20); + $cache->save(); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/Psr6CacheTest.php b/vendor/league/flysystem-cached-adapter/tests/Psr6CacheTest.php new file mode 100644 index 0000000..d5e5700 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/Psr6CacheTest.php @@ -0,0 +1,45 @@ +shouldReceive('isHit')->once()->andReturn(false); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Psr6Cache($pool); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $pool = Mockery::mock('Psr\Cache\CacheItemPoolInterface'); + $item = Mockery::mock('Psr\Cache\CacheItemInterface'); + $item->shouldReceive('get')->once()->andReturn($response); + $item->shouldReceive('isHit')->once()->andReturn(true); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Psr6Cache($pool); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $response = json_encode([[], []]); + $ttl = 4711; + $pool = Mockery::mock('Psr\Cache\CacheItemPoolInterface'); + $item = Mockery::mock('Psr\Cache\CacheItemInterface'); + $item->shouldReceive('expiresAfter')->once()->with($ttl); + $item->shouldReceive('set')->once()->andReturn($response); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $pool->shouldReceive('save')->once()->with($item); + $cache = new Psr6Cache($pool, 'foo', $ttl); + $cache->save(); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/StashTest.php b/vendor/league/flysystem-cached-adapter/tests/StashTest.php new file mode 100644 index 0000000..29e142d --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/StashTest.php @@ -0,0 +1,43 @@ +shouldReceive('get')->once()->andReturn(null); + $item->shouldReceive('isMiss')->once()->andReturn(true); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Stash($pool); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $pool = Mockery::mock('Stash\Pool'); + $item = Mockery::mock('Stash\Item'); + $item->shouldReceive('get')->once()->andReturn($response); + $item->shouldReceive('isMiss')->once()->andReturn(false); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Stash($pool); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $response = json_encode([[], []]); + $pool = Mockery::mock('Stash\Pool'); + $item = Mockery::mock('Stash\Item'); + $item->shouldReceive('set')->once()->andReturn($response); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Stash($pool); + $cache->save(); + } +} diff --git a/vendor/league/flysystem/LICENSE b/vendor/league/flysystem/LICENSE new file mode 100644 index 0000000..f2684c8 --- /dev/null +++ b/vendor/league/flysystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2019 Frank de Jonge + +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/league/flysystem/composer.json b/vendor/league/flysystem/composer.json new file mode 100644 index 0000000..84229e9 --- /dev/null +++ b/vendor/league/flysystem/composer.json @@ -0,0 +1,64 @@ +{ + "name": "league/flysystem", + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "filesystem", "filesystems", "files", "storage", "dropbox", "aws", + "abstraction", "s3", "ftp", "sftp", "remote", "webdav", + "file systems", "cloud", "cloud files", "rackspace", "copy.com" + ], + "license": "MIT", + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "require": { + "php": ">=5.5.9", + "ext-fileinfo": "*" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7.10" + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\Flysystem\\Stub\\": "stub/" + }, + "files": [ + "tests/PHPUnitHacks.php" + ] + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "scripts": { + "phpstan": "php phpstan.php" + } +} diff --git a/vendor/league/flysystem/deprecations.md b/vendor/league/flysystem/deprecations.md new file mode 100644 index 0000000..c336a42 --- /dev/null +++ b/vendor/league/flysystem/deprecations.md @@ -0,0 +1,19 @@ +# Deprecations + +This document lists all the planned deprecations. + +## Handlers will be removed in 2.0 + +The `Handler` type and associated calls will be removed in version 2.0. + +### Upgrade path + +You should create your own implementation for handling OOP usage, +but it's recommended to move away from using an OOP-style wrapper entirely. + +The reason for this is that it's too easy for implementation details (for +your application this is Flysystem) to leak into the application. The most +important part for Flysystem is that it improves portability and creates a +solid boundary between your application core and the infrastructure you use. +The OOP-style handling breaks this principle, therefore I want to stop +promoting it. diff --git a/vendor/league/flysystem/src/Adapter/AbstractAdapter.php b/vendor/league/flysystem/src/Adapter/AbstractAdapter.php new file mode 100644 index 0000000..e577ac4 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/AbstractAdapter.php @@ -0,0 +1,72 @@ +pathPrefix = null; + + return; + } + + $this->pathPrefix = rtrim($prefix, '\\/') . $this->pathSeparator; + } + + /** + * Get the path prefix. + * + * @return string|null path prefix or null if pathPrefix is empty + */ + public function getPathPrefix() + { + return $this->pathPrefix; + } + + /** + * Prefix a path. + * + * @param string $path + * + * @return string prefixed path + */ + public function applyPathPrefix($path) + { + return $this->getPathPrefix() . ltrim($path, '\\/'); + } + + /** + * Remove a path prefix. + * + * @param string $path + * + * @return string path without the prefix + */ + public function removePathPrefix($path) + { + return substr($path, strlen($this->getPathPrefix())); + } +} diff --git a/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php b/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php new file mode 100644 index 0000000..578b491 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php @@ -0,0 +1,693 @@ +safeStorage = new SafeStorage(); + $this->setConfig($config); + } + + /** + * Set the config. + * + * @param array $config + * + * @return $this + */ + public function setConfig(array $config) + { + foreach ($this->configurable as $setting) { + if ( ! isset($config[$setting])) { + continue; + } + + $method = 'set' . ucfirst($setting); + + if (method_exists($this, $method)) { + $this->$method($config[$setting]); + } + } + + return $this; + } + + /** + * Returns the host. + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the host. + * + * @param string $host + * + * @return $this + */ + public function setHost($host) + { + $this->host = $host; + + return $this; + } + + /** + * Set the public permission value. + * + * @param int $permPublic + * + * @return $this + */ + public function setPermPublic($permPublic) + { + $this->permPublic = $permPublic; + + return $this; + } + + /** + * Set the private permission value. + * + * @param int $permPrivate + * + * @return $this + */ + public function setPermPrivate($permPrivate) + { + $this->permPrivate = $permPrivate; + + return $this; + } + + /** + * Returns the ftp port. + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Returns the root folder to work from. + * + * @return string + */ + public function getRoot() + { + return $this->root; + } + + /** + * Set the ftp port. + * + * @param int|string $port + * + * @return $this + */ + public function setPort($port) + { + $this->port = (int) $port; + + return $this; + } + + /** + * Set the root folder to work from. + * + * @param string $root + * + * @return $this + */ + public function setRoot($root) + { + $this->root = rtrim($root, '\\/') . $this->separator; + + return $this; + } + + /** + * Returns the ftp username. + * + * @return string username + */ + public function getUsername() + { + $username = $this->safeStorage->retrieveSafely('username'); + + return $username !== null ? $username : 'anonymous'; + } + + /** + * Set ftp username. + * + * @param string $username + * + * @return $this + */ + public function setUsername($username) + { + $this->safeStorage->storeSafely('username', $username); + + return $this; + } + + /** + * Returns the password. + * + * @return string password + */ + public function getPassword() + { + return $this->safeStorage->retrieveSafely('password'); + } + + /** + * Set the ftp password. + * + * @param string $password + * + * @return $this + */ + public function setPassword($password) + { + $this->safeStorage->storeSafely('password', $password); + + return $this; + } + + /** + * Returns the amount of seconds before the connection will timeout. + * + * @return int + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Set the amount of seconds before the connection should timeout. + * + * @param int $timeout + * + * @return $this + */ + public function setTimeout($timeout) + { + $this->timeout = (int) $timeout; + + return $this; + } + + /** + * Return the FTP system type. + * + * @return string + */ + public function getSystemType() + { + return $this->systemType; + } + + /** + * Set the FTP system type (windows or unix). + * + * @param string $systemType + * + * @return $this + */ + public function setSystemType($systemType) + { + $this->systemType = strtolower($systemType); + + return $this; + } + + /** + * True to enable timestamps for FTP servers that return unix-style listings. + * + * @param bool $bool + * + * @return $this + */ + public function setEnableTimestampsOnUnixListings($bool = false) + { + $this->enableTimestampsOnUnixListings = $bool; + + return $this; + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + return $this->listDirectoryContents($directory, $recursive); + } + + abstract protected function listDirectoryContents($directory, $recursive = false); + + /** + * Normalize a directory listing. + * + * @param array $listing + * @param string $prefix + * + * @return array directory listing + */ + protected function normalizeListing(array $listing, $prefix = '') + { + $base = $prefix; + $result = []; + $listing = $this->removeDotDirectories($listing); + + while ($item = array_shift($listing)) { + if (preg_match('#^.*:$#', $item)) { + $base = preg_replace('~^\./*|:$~', '', $item); + continue; + } + + $result[] = $this->normalizeObject($item, $base); + } + + return $this->sortListing($result); + } + + /** + * Sort a directory listing. + * + * @param array $result + * + * @return array sorted listing + */ + protected function sortListing(array $result) + { + $compare = function ($one, $two) { + return strnatcmp($one['path'], $two['path']); + }; + + usort($result, $compare); + + return $result; + } + + /** + * Normalize a file entry. + * + * @param string $item + * @param string $base + * + * @return array normalized file array + * + * @throws NotSupportedException + */ + protected function normalizeObject($item, $base) + { + $systemType = $this->systemType ?: $this->detectSystemType($item); + + if ($systemType === 'unix') { + return $this->normalizeUnixObject($item, $base); + } elseif ($systemType === 'windows') { + return $this->normalizeWindowsObject($item, $base); + } + + throw NotSupportedException::forFtpSystemType($systemType); + } + + /** + * Normalize a Unix file entry. + * + * Given $item contains: + * '-rw-r--r-- 1 ftp ftp 409 Aug 19 09:01 file1.txt' + * + * This function will return: + * [ + * 'type' => 'file', + * 'path' => 'file1.txt', + * 'visibility' => 'public', + * 'size' => 409, + * 'timestamp' => 1566205260 + * ] + * + * @param string $item + * @param string $base + * + * @return array normalized file array + */ + protected function normalizeUnixObject($item, $base) + { + $item = preg_replace('#\s+#', ' ', trim($item), 7); + + if (count(explode(' ', $item, 9)) !== 9) { + throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts."); + } + + list($permissions, /* $number */, /* $owner */, /* $group */, $size, $month, $day, $timeOrYear, $name) = explode(' ', $item, 9); + $type = $this->detectType($permissions); + $path = $base === '' ? $name : $base . $this->separator . $name; + + if ($type === 'dir') { + return compact('type', 'path'); + } + + $permissions = $this->normalizePermissions($permissions); + $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE; + $size = (int) $size; + + $result = compact('type', 'path', 'visibility', 'size'); + if ($this->enableTimestampsOnUnixListings) { + $timestamp = $this->normalizeUnixTimestamp($month, $day, $timeOrYear); + $result += compact('timestamp'); + } + + return $result; + } + + /** + * Only accurate to the minute (current year), or to the day. + * + * Inadequacies in timestamp accuracy are due to limitations of the FTP 'LIST' command + * + * Note: The 'MLSD' command is a machine-readable replacement for 'LIST' + * but many FTP servers do not support it :( + * + * @param string $month e.g. 'Aug' + * @param string $day e.g. '19' + * @param string $timeOrYear e.g. '09:01' OR '2015' + * + * @return int + */ + protected function normalizeUnixTimestamp($month, $day, $timeOrYear) + { + if (is_numeric($timeOrYear)) { + $year = $timeOrYear; + $hour = '00'; + $minute = '00'; + $seconds = '00'; + } else { + $year = date('Y'); + list($hour, $minute) = explode(':', $timeOrYear); + $seconds = '00'; + } + $dateTime = DateTime::createFromFormat('Y-M-j-G:i:s', "{$year}-{$month}-{$day}-{$hour}:{$minute}:{$seconds}"); + + return $dateTime->getTimestamp(); + } + + /** + * Normalize a Windows/DOS file entry. + * + * @param string $item + * @param string $base + * + * @return array normalized file array + */ + protected function normalizeWindowsObject($item, $base) + { + $item = preg_replace('#\s+#', ' ', trim($item), 3); + + if (count(explode(' ', $item, 4)) !== 4) { + throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts."); + } + + list($date, $time, $size, $name) = explode(' ', $item, 4); + $path = $base === '' ? $name : $base . $this->separator . $name; + + // Check for the correct date/time format + $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i'; + $dt = DateTime::createFromFormat($format, $date . $time); + $timestamp = $dt ? $dt->getTimestamp() : (int) strtotime("$date $time"); + + if ($size === '') { + $type = 'dir'; + + return compact('type', 'path', 'timestamp'); + } + + $type = 'file'; + $visibility = AdapterInterface::VISIBILITY_PUBLIC; + $size = (int) $size; + + return compact('type', 'path', 'visibility', 'size', 'timestamp'); + } + + /** + * Get the system type from a listing item. + * + * @param string $item + * + * @return string the system type + */ + protected function detectSystemType($item) + { + return preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', $item) ? 'windows' : 'unix'; + } + + /** + * Get the file type from the permissions. + * + * @param string $permissions + * + * @return string file type + */ + protected function detectType($permissions) + { + return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file'; + } + + /** + * Normalize a permissions string. + * + * @param string $permissions + * + * @return int + */ + protected function normalizePermissions($permissions) + { + // remove the type identifier + $permissions = substr($permissions, 1); + + // map the string rights to the numeric counterparts + $map = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1']; + $permissions = strtr($permissions, $map); + + // split up the permission groups + $parts = str_split($permissions, 3); + + // convert the groups + $mapper = function ($part) { + return array_sum(str_split($part)); + }; + + // converts to decimal number + return octdec(implode('', array_map($mapper, $parts))); + } + + /** + * Filter out dot-directories. + * + * @param array $list + * + * @return array + */ + public function removeDotDirectories(array $list) + { + $filter = function ($line) { + return $line !== '' && ! preg_match('#.* \.(\.)?$|^total#', $line); + }; + + return array_filter($list, $filter); + } + + /** + * @inheritdoc + */ + public function has($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + return $this->getMetadata($path); + } + + /** + * Ensure a directory exists. + * + * @param string $dirname + */ + public function ensureDirectory($dirname) + { + $dirname = (string) $dirname; + + if ($dirname !== '' && ! $this->has($dirname)) { + $this->createDir($dirname, new Config()); + } + } + + /** + * @return mixed + */ + public function getConnection() + { + $tries = 0; + + while ( ! $this->isConnected() && $tries < 3) { + $tries++; + $this->disconnect(); + $this->connect(); + } + + return $this->connection; + } + + /** + * Get the public permission value. + * + * @return int + */ + public function getPermPublic() + { + return $this->permPublic; + } + + /** + * Get the private permission value. + * + * @return int + */ + public function getPermPrivate() + { + return $this->permPrivate; + } + + /** + * Disconnect on destruction. + */ + public function __destruct() + { + $this->disconnect(); + } + + /** + * Establish a connection. + */ + abstract public function connect(); + + /** + * Close the connection. + */ + abstract public function disconnect(); + + /** + * Check if a connection is active. + * + * @return bool + */ + abstract public function isConnected(); +} diff --git a/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php b/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php new file mode 100644 index 0000000..fd8d216 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php @@ -0,0 +1,12 @@ +transferMode = $mode; + + return $this; + } + + /** + * Set if Ssl is enabled. + * + * @param bool $ssl + * + * @return $this + */ + public function setSsl($ssl) + { + $this->ssl = (bool) $ssl; + + return $this; + } + + /** + * Set if passive mode should be used. + * + * @param bool $passive + */ + public function setPassive($passive = true) + { + $this->passive = $passive; + } + + /** + * @param bool $ignorePassiveAddress + */ + public function setIgnorePassiveAddress($ignorePassiveAddress) + { + $this->ignorePassiveAddress = $ignorePassiveAddress; + } + + /** + * @param bool $recurseManually + */ + public function setRecurseManually($recurseManually) + { + $this->recurseManually = $recurseManually; + } + + /** + * @param bool $utf8 + */ + public function setUtf8($utf8) + { + $this->utf8 = (bool) $utf8; + } + + /** + * Connect to the FTP server. + */ + public function connect() + { + if ($this->ssl) { + $this->connection = ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout()); + } else { + $this->connection = ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout()); + } + + if ( ! $this->connection) { + throw new RuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort()); + } + + $this->login(); + $this->setUtf8Mode(); + $this->setConnectionPassiveMode(); + $this->setConnectionRoot(); + $this->isPureFtpd = $this->isPureFtpdServer(); + } + + /** + * Set the connection to UTF-8 mode. + */ + protected function setUtf8Mode() + { + if ($this->utf8) { + $response = ftp_raw($this->connection, "OPTS UTF8 ON"); + if (substr($response[0], 0, 3) !== '200') { + throw new RuntimeException( + 'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort() + ); + } + } + } + + /** + * Set the connections to passive mode. + * + * @throws RuntimeException + */ + protected function setConnectionPassiveMode() + { + if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) { + ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress); + } + + if ( ! ftp_pasv($this->connection, $this->passive)) { + throw new RuntimeException( + 'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort() + ); + } + } + + /** + * Set the connection root. + */ + protected function setConnectionRoot() + { + $root = $this->getRoot(); + $connection = $this->connection; + + if ($root && ! ftp_chdir($connection, $root)) { + throw new RuntimeException('Root is invalid or does not exist: ' . $this->getRoot()); + } + + // Store absolute path for further reference. + // This is needed when creating directories and + // initial root was a relative path, else the root + // would be relative to the chdir'd path. + $this->root = ftp_pwd($connection); + } + + /** + * Login. + * + * @throws RuntimeException + */ + protected function login() + { + set_error_handler(function () { + }); + $isLoggedIn = ftp_login( + $this->connection, + $this->getUsername(), + $this->getPassword() + ); + restore_error_handler(); + + if ( ! $isLoggedIn) { + $this->disconnect(); + throw new RuntimeException( + 'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort( + ) . ', username: ' . $this->getUsername() + ); + } + } + + /** + * Disconnect from the FTP server. + */ + public function disconnect() + { + if (is_resource($this->connection)) { + ftp_close($this->connection); + } + + $this->connection = null; + } + + /** + * @inheritdoc + */ + public function write($path, $contents, Config $config) + { + $stream = fopen('php://temp', 'w+b'); + fwrite($stream, $contents); + rewind($stream); + $result = $this->writeStream($path, $stream, $config); + fclose($stream); + + if ($result === false) { + return false; + } + + $result['contents'] = $contents; + $result['mimetype'] = $config->get('mimetype') ?: Util::guessMimeType($path, $contents); + + return $result; + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, Config $config) + { + $this->ensureDirectory(Util::dirname($path)); + + if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) { + return false; + } + + if ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + } + + $type = 'file'; + + return compact('type', 'path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + return $this->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, Config $config) + { + return $this->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + return ftp_rename($this->getConnection(), $path, $newpath); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + return ftp_delete($this->getConnection(), $path); + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $connection = $this->getConnection(); + $contents = array_reverse($this->listDirectoryContents($dirname, false)); + + foreach ($contents as $object) { + if ($object['type'] === 'file') { + if ( ! ftp_delete($connection, $object['path'])) { + return false; + } + } elseif ( ! $this->deleteDir($object['path'])) { + return false; + } + } + + return ftp_rmdir($connection, $dirname); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + $connection = $this->getConnection(); + $directories = explode('/', $dirname); + + foreach ($directories as $directory) { + if (false === $this->createActualDirectory($directory, $connection)) { + $this->setConnectionRoot(); + + return false; + } + + ftp_chdir($connection, $directory); + } + + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $dirname]; + } + + /** + * Create a directory. + * + * @param string $directory + * @param resource $connection + * + * @return bool + */ + protected function createActualDirectory($directory, $connection) + { + // List the current directory + $listing = ftp_nlist($connection, '.') ?: []; + + foreach ($listing as $key => $item) { + if (preg_match('~^\./.*~', $item)) { + $listing[$key] = substr($item, 2); + } + } + + if (in_array($directory, $listing, true)) { + return true; + } + + return (boolean) ftp_mkdir($connection, $directory); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + if ($path === '') { + return ['type' => 'dir', 'path' => '']; + } + + if (@ftp_chdir($this->getConnection(), $path) === true) { + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $path]; + } + + $listing = $this->ftpRawlist('-A', str_replace('*', '\\*', $path)); + + if (empty($listing) || in_array('total 0', $listing, true)) { + return false; + } + + if (preg_match('/.* not found/', $listing[0])) { + return false; + } + + if (preg_match('/^total [0-9]*$/', $listing[0])) { + array_shift($listing); + } + + return $this->normalizeObject($listing[0], ''); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + if ( ! $metadata = $this->getMetadata($path)) { + return false; + } + + $metadata['mimetype'] = MimeType::detectByFilename($path); + + return $metadata; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + $timestamp = ftp_mdtm($this->getConnection(), $path); + + return ($timestamp !== -1) ? ['path' => $path, 'timestamp' => $timestamp] : false; + } + + /** + * @inheritdoc + */ + public function read($path) + { + if ( ! $object = $this->readStream($path)) { + return false; + } + + $object['contents'] = stream_get_contents($object['stream']); + fclose($object['stream']); + unset($object['stream']); + + return $object; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $stream = fopen('php://temp', 'w+b'); + $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode); + rewind($stream); + + if ( ! $result) { + fclose($stream); + + return false; + } + + return ['type' => 'file', 'path' => $path, 'stream' => $stream]; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $mode = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? $this->getPermPublic() : $this->getPermPrivate(); + + if ( ! ftp_chmod($this->getConnection(), $mode, $path)) { + return false; + } + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + * + * @param string $directory + */ + protected function listDirectoryContents($directory, $recursive = true) + { + $directory = str_replace('*', '\\*', $directory); + + if ($recursive && $this->recurseManually) { + return $this->listDirectoryContentsRecursive($directory); + } + + $options = $recursive ? '-alnR' : '-aln'; + $listing = $this->ftpRawlist($options, $directory); + + return $listing ? $this->normalizeListing($listing, $directory) : []; + } + + /** + * @inheritdoc + * + * @param string $directory + */ + protected function listDirectoryContentsRecursive($directory) + { + $listing = $this->normalizeListing($this->ftpRawlist('-aln', $directory) ?: [], $directory); + $output = []; + + foreach ($listing as $item) { + $output[] = $item; + if ($item['type'] !== 'dir') { + continue; + } + $output = array_merge($output, $this->listDirectoryContentsRecursive($item['path'])); + } + + return $output; + } + + /** + * Check if the connection is open. + * + * @return bool + * + * @throws ErrorException + */ + public function isConnected() + { + try { + return is_resource($this->connection) && ftp_rawlist($this->connection, $this->getRoot()) !== false; + } catch (ErrorException $e) { + if (strpos($e->getMessage(), 'ftp_rawlist') === false) { + throw $e; + } + + return false; + } + } + + /** + * @return bool + */ + protected function isPureFtpdServer() + { + $response = ftp_raw($this->connection, 'HELP'); + + return stripos(implode(' ', $response), 'Pure-FTPd') !== false; + } + + /** + * The ftp_rawlist function with optional escaping. + * + * @param string $options + * @param string $path + * + * @return array + */ + protected function ftpRawlist($options, $path) + { + $connection = $this->getConnection(); + + if ($this->isPureFtpd) { + $path = str_replace(' ', '\ ', $path); + } + + return ftp_rawlist($connection, $options . ' ' . $path); + } +} diff --git a/vendor/league/flysystem/src/Adapter/Ftpd.php b/vendor/league/flysystem/src/Adapter/Ftpd.php new file mode 100644 index 0000000..d5349e4 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Ftpd.php @@ -0,0 +1,45 @@ + 'dir', 'path' => '']; + } + if (@ftp_chdir($this->getConnection(), $path) === true) { + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $path]; + } + + if ( ! ($object = ftp_raw($this->getConnection(), 'STAT ' . $path)) || count($object) < 3) { + return false; + } + + if (substr($object[1], 0, 5) === "ftpd:") { + return false; + } + + return $this->normalizeObject($object[1], ''); + } + + /** + * @inheritdoc + */ + protected function listDirectoryContents($directory, $recursive = true) + { + $listing = ftp_rawlist($this->getConnection(), $directory, $recursive); + + if ($listing === false || ( ! empty($listing) && substr($listing[0], 0, 5) === "ftpd:")) { + return []; + } + + return $this->normalizeListing($listing, $directory); + } +} diff --git a/vendor/league/flysystem/src/Adapter/Local.php b/vendor/league/flysystem/src/Adapter/Local.php new file mode 100644 index 0000000..2b892ab --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Local.php @@ -0,0 +1,532 @@ + [ + 'public' => 0644, + 'private' => 0600, + ], + 'dir' => [ + 'public' => 0755, + 'private' => 0700, + ], + ]; + + /** + * @var string + */ + protected $pathSeparator = DIRECTORY_SEPARATOR; + + /** + * @var array + */ + protected $permissionMap; + + /** + * @var int + */ + protected $writeFlags; + + /** + * @var int + */ + private $linkHandling; + + /** + * Constructor. + * + * @param string $root + * @param int $writeFlags + * @param int $linkHandling + * @param array $permissions + * + * @throws LogicException + */ + public function __construct($root, $writeFlags = LOCK_EX, $linkHandling = self::DISALLOW_LINKS, array $permissions = []) + { + $root = is_link($root) ? realpath($root) : $root; + $this->permissionMap = array_replace_recursive(static::$permissions, $permissions); + $this->ensureDirectory($root); + + if ( ! is_dir($root) || ! is_readable($root)) { + throw new LogicException('The root path ' . $root . ' is not readable.'); + } + + $this->setPathPrefix($root); + $this->writeFlags = $writeFlags; + $this->linkHandling = $linkHandling; + } + + /** + * Ensure the root directory exists. + * + * @param string $root root directory path + * + * @return void + * + * @throws Exception in case the root directory can not be created + */ + protected function ensureDirectory($root) + { + if ( ! is_dir($root)) { + $umask = umask(0); + + if ( ! @mkdir($root, $this->permissionMap['dir']['public'], true)) { + $mkdirError = error_get_last(); + } + + umask($umask); + clearstatcache(false, $root); + + if ( ! is_dir($root)) { + $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : ''; + throw new Exception(sprintf('Impossible to create the root directory "%s". %s', $root, $errorMessage)); + } + } + } + + /** + * @inheritdoc + */ + public function has($path) + { + $location = $this->applyPathPrefix($path); + + return file_exists($location); + } + + /** + * @inheritdoc + */ + public function write($path, $contents, Config $config) + { + $location = $this->applyPathPrefix($path); + $this->ensureDirectory(dirname($location)); + + if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) { + return false; + } + + $type = 'file'; + $result = compact('contents', 'type', 'size', 'path'); + + if ($visibility = $config->get('visibility')) { + $result['visibility'] = $visibility; + $this->setVisibility($path, $visibility); + } + + return $result; + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, Config $config) + { + $location = $this->applyPathPrefix($path); + $this->ensureDirectory(dirname($location)); + $stream = fopen($location, 'w+b'); + + if ( ! $stream || stream_copy_to_stream($resource, $stream) === false || ! fclose($stream)) { + return false; + } + + $type = 'file'; + $result = compact('type', 'path'); + + if ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + $result['visibility'] = $visibility; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $location = $this->applyPathPrefix($path); + $stream = fopen($location, 'rb'); + + return ['type' => 'file', 'path' => $path, 'stream' => $stream]; + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, Config $config) + { + return $this->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + $location = $this->applyPathPrefix($path); + $size = file_put_contents($location, $contents, $this->writeFlags); + + if ($size === false) { + return false; + } + + $type = 'file'; + + $result = compact('type', 'path', 'size', 'contents'); + + if ($mimetype = $config->get('mimetype') ?: Util::guessMimeType($path, $contents)) { + $result['mimetype'] = $mimetype; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function read($path) + { + $location = $this->applyPathPrefix($path); + $contents = @file_get_contents($location); + + if ($contents === false) { + return false; + } + + return ['type' => 'file', 'path' => $path, 'contents' => $contents]; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + $location = $this->applyPathPrefix($path); + $destination = $this->applyPathPrefix($newpath); + $parentDirectory = $this->applyPathPrefix(Util::dirname($newpath)); + $this->ensureDirectory($parentDirectory); + + return rename($location, $destination); + } + + /** + * @inheritdoc + */ + public function copy($path, $newpath) + { + $location = $this->applyPathPrefix($path); + $destination = $this->applyPathPrefix($newpath); + $this->ensureDirectory(dirname($destination)); + + return copy($location, $destination); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + $location = $this->applyPathPrefix($path); + + return @unlink($location); + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + $result = []; + $location = $this->applyPathPrefix($directory); + + if ( ! is_dir($location)) { + return []; + } + + $iterator = $recursive ? $this->getRecursiveDirectoryIterator($location) : $this->getDirectoryIterator($location); + + foreach ($iterator as $file) { + $path = $this->getFilePath($file); + + if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) { + continue; + } + + $result[] = $this->normalizeFileInfo($file); + } + + unset($iterator); + + return array_filter($result); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + $location = $this->applyPathPrefix($path); + clearstatcache(false, $location); + $info = new SplFileInfo($location); + + return $this->normalizeFileInfo($info); + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + $location = $this->applyPathPrefix($path); + $finfo = new Finfo(FILEINFO_MIME_TYPE); + $mimetype = $finfo->file($location); + + if (in_array($mimetype, ['application/octet-stream', 'inode/x-empty', 'application/x-empty'])) { + $mimetype = Util\MimeType::detectByFilename($location); + } + + return ['path' => $path, 'type' => 'file', 'mimetype' => $mimetype]; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + $location = $this->applyPathPrefix($path); + clearstatcache(false, $location); + $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4)); + $type = is_dir($location) ? 'dir' : 'file'; + + foreach ($this->permissionMap[$type] as $visibility => $visibilityPermissions) { + if ($visibilityPermissions == $permissions) { + return compact('path', 'visibility'); + } + } + + $visibility = substr(sprintf('%o', fileperms($location)), -4); + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $location = $this->applyPathPrefix($path); + $type = is_dir($location) ? 'dir' : 'file'; + $success = chmod($location, $this->permissionMap[$type][$visibility]); + + if ($success === false) { + return false; + } + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + $location = $this->applyPathPrefix($dirname); + $umask = umask(0); + $visibility = $config->get('visibility', 'public'); + $return = ['path' => $dirname, 'type' => 'dir']; + + if ( ! is_dir($location)) { + if (false === @mkdir($location, $this->permissionMap['dir'][$visibility], true) + || false === is_dir($location)) { + $return = false; + } + } + + umask($umask); + + return $return; + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $location = $this->applyPathPrefix($dirname); + + if ( ! is_dir($location)) { + return false; + } + + $contents = $this->getRecursiveDirectoryIterator($location, RecursiveIteratorIterator::CHILD_FIRST); + + /** @var SplFileInfo $file */ + foreach ($contents as $file) { + $this->guardAgainstUnreadableFileInfo($file); + $this->deleteFileInfoObject($file); + } + + unset($contents); + + return rmdir($location); + } + + /** + * @param SplFileInfo $file + */ + protected function deleteFileInfoObject(SplFileInfo $file) + { + switch ($file->getType()) { + case 'dir': + rmdir($file->getRealPath()); + break; + case 'link': + unlink($file->getPathname()); + break; + default: + unlink($file->getRealPath()); + } + } + + /** + * Normalize the file info. + * + * @param SplFileInfo $file + * + * @return array|void + * + * @throws NotSupportedException + */ + protected function normalizeFileInfo(SplFileInfo $file) + { + if ( ! $file->isLink()) { + return $this->mapFileInfo($file); + } + + if ($this->linkHandling & self::DISALLOW_LINKS) { + throw NotSupportedException::forLink($file); + } + } + + /** + * Get the normalized path from a SplFileInfo object. + * + * @param SplFileInfo $file + * + * @return string + */ + protected function getFilePath(SplFileInfo $file) + { + $location = $file->getPathname(); + $path = $this->removePathPrefix($location); + + return trim(str_replace('\\', '/', $path), '/'); + } + + /** + * @param string $path + * @param int $mode + * + * @return RecursiveIteratorIterator + */ + protected function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST) + { + return new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), + $mode + ); + } + + /** + * @param string $path + * + * @return DirectoryIterator + */ + protected function getDirectoryIterator($path) + { + $iterator = new DirectoryIterator($path); + + return $iterator; + } + + /** + * @param SplFileInfo $file + * + * @return array + */ + protected function mapFileInfo(SplFileInfo $file) + { + $normalized = [ + 'type' => $file->getType(), + 'path' => $this->getFilePath($file), + ]; + + $normalized['timestamp'] = $file->getMTime(); + + if ($normalized['type'] === 'file') { + $normalized['size'] = $file->getSize(); + } + + return $normalized; + } + + /** + * @param SplFileInfo $file + * + * @throws UnreadableFileException + */ + protected function guardAgainstUnreadableFileInfo(SplFileInfo $file) + { + if ( ! $file->isReadable()) { + throw UnreadableFileException::forFileInfo($file); + } + } +} diff --git a/vendor/league/flysystem/src/Adapter/NullAdapter.php b/vendor/league/flysystem/src/Adapter/NullAdapter.php new file mode 100644 index 0000000..2527087 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/NullAdapter.php @@ -0,0 +1,144 @@ +get('visibility')) { + $result['visibility'] = $visibility; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + return false; + } + + /** + * @inheritdoc + */ + public function read($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + return false; + } + + /** + * @inheritdoc + */ + public function delete($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + return []; + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + return compact('visibility'); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + return ['path' => $dirname, 'type' => 'dir']; + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + return false; + } +} diff --git a/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php b/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php new file mode 100644 index 0000000..fc0a747 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php @@ -0,0 +1,33 @@ +readStream($path); + + if ($response === false || ! is_resource($response['stream'])) { + return false; + } + + $result = $this->writeStream($newpath, $response['stream'], new Config()); + + if ($result !== false && is_resource($response['stream'])) { + fclose($response['stream']); + } + + return $result !== false; + } + + // Required abstract method + + /** + * @param string $path + * + * @return resource + */ + abstract public function readStream($path); + + /** + * @param string $path + * @param resource $resource + * @param Config $config + * + * @return resource + */ + abstract public function writeStream($path, $resource, Config $config); +} diff --git a/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php new file mode 100644 index 0000000..2b31c01 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php @@ -0,0 +1,44 @@ +read($path)) { + return false; + } + + $stream = fopen('php://temp', 'w+b'); + fwrite($stream, $data['contents']); + rewind($stream); + $data['stream'] = $stream; + unset($data['contents']); + + return $data; + } + + /** + * Reads a file. + * + * @param string $path + * + * @return array|false + * + * @see League\Flysystem\ReadInterface::read() + */ + abstract public function read($path); +} diff --git a/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php new file mode 100644 index 0000000..8042496 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php @@ -0,0 +1,9 @@ +stream($path, $resource, $config, 'write'); + } + + /** + * Update a file using a stream. + * + * @param string $path + * @param resource $resource + * @param Config $config Config object or visibility setting + * + * @return mixed false of file metadata + */ + public function updateStream($path, $resource, Config $config) + { + return $this->stream($path, $resource, $config, 'update'); + } + + // Required abstract methods + abstract public function write($pash, $contents, Config $config); + abstract public function update($pash, $contents, Config $config); +} diff --git a/vendor/league/flysystem/src/Adapter/SynologyFtp.php b/vendor/league/flysystem/src/Adapter/SynologyFtp.php new file mode 100644 index 0000000..fe0d344 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/SynologyFtp.php @@ -0,0 +1,8 @@ +settings = $settings; + } + + /** + * Get a setting. + * + * @param string $key + * @param mixed $default + * + * @return mixed config setting or default when not found + */ + public function get($key, $default = null) + { + if ( ! array_key_exists($key, $this->settings)) { + return $this->getDefault($key, $default); + } + + return $this->settings[$key]; + } + + /** + * Check if an item exists by key. + * + * @param string $key + * + * @return bool + */ + public function has($key) + { + if (array_key_exists($key, $this->settings)) { + return true; + } + + return $this->fallback instanceof Config + ? $this->fallback->has($key) + : false; + } + + /** + * Try to retrieve a default setting from a config fallback. + * + * @param string $key + * @param mixed $default + * + * @return mixed config setting or default when not found + */ + protected function getDefault($key, $default) + { + if ( ! $this->fallback) { + return $default; + } + + return $this->fallback->get($key, $default); + } + + /** + * Set a setting. + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function set($key, $value) + { + $this->settings[$key] = $value; + + return $this; + } + + /** + * Set the fallback. + * + * @param Config $fallback + * + * @return $this + */ + public function setFallback(Config $fallback) + { + $this->fallback = $fallback; + + return $this; + } +} diff --git a/vendor/league/flysystem/src/ConfigAwareTrait.php b/vendor/league/flysystem/src/ConfigAwareTrait.php new file mode 100644 index 0000000..202d605 --- /dev/null +++ b/vendor/league/flysystem/src/ConfigAwareTrait.php @@ -0,0 +1,49 @@ +config = $config ? Util::ensureConfig($config) : new Config; + } + + /** + * Get the Config. + * + * @return Config config object + */ + public function getConfig() + { + return $this->config; + } + + /** + * Convert a config array to a Config object with the correct fallback. + * + * @param array $config + * + * @return Config + */ + protected function prepareConfig(array $config) + { + $config = new Config($config); + $config->setFallback($this->getConfig()); + + return $config; + } +} diff --git a/vendor/league/flysystem/src/Directory.php b/vendor/league/flysystem/src/Directory.php new file mode 100644 index 0000000..d4f90a8 --- /dev/null +++ b/vendor/league/flysystem/src/Directory.php @@ -0,0 +1,31 @@ +filesystem->deleteDir($this->path); + } + + /** + * List the directory contents. + * + * @param bool $recursive + * + * @return array|bool directory contents or false + */ + public function getContents($recursive = false) + { + return $this->filesystem->listContents($this->path, $recursive); + } +} diff --git a/vendor/league/flysystem/src/Exception.php b/vendor/league/flysystem/src/Exception.php new file mode 100644 index 0000000..d4a9907 --- /dev/null +++ b/vendor/league/flysystem/src/Exception.php @@ -0,0 +1,8 @@ +filesystem->has($this->path); + } + + /** + * Read the file. + * + * @return string|false file contents + */ + public function read() + { + return $this->filesystem->read($this->path); + } + + /** + * Read the file as a stream. + * + * @return resource|false file stream + */ + public function readStream() + { + return $this->filesystem->readStream($this->path); + } + + /** + * Write the new file. + * + * @param string $content + * + * @return bool success boolean + */ + public function write($content) + { + return $this->filesystem->write($this->path, $content); + } + + /** + * Write the new file using a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function writeStream($resource) + { + return $this->filesystem->writeStream($this->path, $resource); + } + + /** + * Update the file contents. + * + * @param string $content + * + * @return bool success boolean + */ + public function update($content) + { + return $this->filesystem->update($this->path, $content); + } + + /** + * Update the file contents with a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function updateStream($resource) + { + return $this->filesystem->updateStream($this->path, $resource); + } + + /** + * Create the file or update if exists. + * + * @param string $content + * + * @return bool success boolean + */ + public function put($content) + { + return $this->filesystem->put($this->path, $content); + } + + /** + * Create the file or update if exists using a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function putStream($resource) + { + return $this->filesystem->putStream($this->path, $resource); + } + + /** + * Rename the file. + * + * @param string $newpath + * + * @return bool success boolean + */ + public function rename($newpath) + { + if ($this->filesystem->rename($this->path, $newpath)) { + $this->path = $newpath; + + return true; + } + + return false; + } + + /** + * Copy the file. + * + * @param string $newpath + * + * @return File|false new file or false + */ + public function copy($newpath) + { + if ($this->filesystem->copy($this->path, $newpath)) { + return new File($this->filesystem, $newpath); + } + + return false; + } + + /** + * Get the file's timestamp. + * + * @return string|false The timestamp or false on failure. + */ + public function getTimestamp() + { + return $this->filesystem->getTimestamp($this->path); + } + + /** + * Get the file's mimetype. + * + * @return string|false The file mime-type or false on failure. + */ + public function getMimetype() + { + return $this->filesystem->getMimetype($this->path); + } + + /** + * Get the file's visibility. + * + * @return string|false The visibility (public|private) or false on failure. + */ + public function getVisibility() + { + return $this->filesystem->getVisibility($this->path); + } + + /** + * Get the file's metadata. + * + * @return array|false The file metadata or false on failure. + */ + public function getMetadata() + { + return $this->filesystem->getMetadata($this->path); + } + + /** + * Get the file size. + * + * @return int|false The file size or false on failure. + */ + public function getSize() + { + return $this->filesystem->getSize($this->path); + } + + /** + * Delete the file. + * + * @return bool success boolean + */ + public function delete() + { + return $this->filesystem->delete($this->path); + } +} diff --git a/vendor/league/flysystem/src/FileExistsException.php b/vendor/league/flysystem/src/FileExistsException.php new file mode 100644 index 0000000..c82e20c --- /dev/null +++ b/vendor/league/flysystem/src/FileExistsException.php @@ -0,0 +1,37 @@ +path = $path; + + parent::__construct('File already exists at path: ' . $this->getPath(), $code, $previous); + } + + /** + * Get the path which was found. + * + * @return string + */ + public function getPath() + { + return $this->path; + } +} diff --git a/vendor/league/flysystem/src/FileNotFoundException.php b/vendor/league/flysystem/src/FileNotFoundException.php new file mode 100644 index 0000000..989df69 --- /dev/null +++ b/vendor/league/flysystem/src/FileNotFoundException.php @@ -0,0 +1,37 @@ +path = $path; + + parent::__construct('File not found at path: ' . $this->getPath(), $code, $previous); + } + + /** + * Get the path which was not found. + * + * @return string + */ + public function getPath() + { + return $this->path; + } +} diff --git a/vendor/league/flysystem/src/Filesystem.php b/vendor/league/flysystem/src/Filesystem.php new file mode 100644 index 0000000..4509526 --- /dev/null +++ b/vendor/league/flysystem/src/Filesystem.php @@ -0,0 +1,408 @@ +adapter = $adapter; + $this->setConfig($config); + } + + /** + * Get the Adapter. + * + * @return AdapterInterface adapter + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * @inheritdoc + */ + public function has($path) + { + $path = Util::normalizePath($path); + + return strlen($path) === 0 ? false : (bool) $this->getAdapter()->has($path); + } + + /** + * @inheritdoc + */ + public function write($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $this->assertAbsent($path); + $config = $this->prepareConfig($config); + + return (bool) $this->getAdapter()->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $this->assertAbsent($path); + $config = $this->prepareConfig($config); + + Util::rewindStream($resource); + + return (bool) $this->getAdapter()->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function put($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + + if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) { + return (bool) $this->getAdapter()->update($path, $contents, $config); + } + + return (bool) $this->getAdapter()->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function putStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + Util::rewindStream($resource); + + if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) { + return (bool) $this->getAdapter()->updateStream($path, $resource, $config); + } + + return (bool) $this->getAdapter()->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function readAndDelete($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + $contents = $this->read($path); + + if ($contents === false) { + return false; + } + + $this->delete($path); + + return $contents; + } + + /** + * @inheritdoc + */ + public function update($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + + $this->assertPresent($path); + + return (bool) $this->getAdapter()->update($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + $this->assertPresent($path); + Util::rewindStream($resource); + + return (bool) $this->getAdapter()->updateStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function read($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if ( ! ($object = $this->getAdapter()->read($path))) { + return false; + } + + return $object['contents']; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if ( ! $object = $this->getAdapter()->readStream($path)) { + return false; + } + + return $object['stream']; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + $path = Util::normalizePath($path); + $newpath = Util::normalizePath($newpath); + $this->assertPresent($path); + $this->assertAbsent($newpath); + + return (bool) $this->getAdapter()->rename($path, $newpath); + } + + /** + * @inheritdoc + */ + public function copy($path, $newpath) + { + $path = Util::normalizePath($path); + $newpath = Util::normalizePath($newpath); + $this->assertPresent($path); + $this->assertAbsent($newpath); + + return $this->getAdapter()->copy($path, $newpath); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return $this->getAdapter()->delete($path); + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $dirname = Util::normalizePath($dirname); + + if ($dirname === '') { + throw new RootViolationException('Root directories can not be deleted.'); + } + + return (bool) $this->getAdapter()->deleteDir($dirname); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, array $config = []) + { + $dirname = Util::normalizePath($dirname); + $config = $this->prepareConfig($config); + + return (bool) $this->getAdapter()->createDir($dirname, $config); + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + $directory = Util::normalizePath($directory); + $contents = $this->getAdapter()->listContents($directory, $recursive); + + return (new ContentListingFormatter($directory, $recursive, $this->config->get('case_sensitive', true))) + ->formatListing($contents); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getMimetype($path)) || ! array_key_exists('mimetype', $object)) { + return false; + } + + return $object['mimetype']; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getTimestamp($path)) || ! array_key_exists('timestamp', $object)) { + return false; + } + + return $object['timestamp']; + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getVisibility($path)) || ! array_key_exists('visibility', $object)) { + return false; + } + + return $object['visibility']; + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getSize($path)) || ! array_key_exists('size', $object)) { + return false; + } + + return (int) $object['size']; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return (bool) $this->getAdapter()->setVisibility($path, $visibility); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return $this->getAdapter()->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function get($path, Handler $handler = null) + { + $path = Util::normalizePath($path); + + if ( ! $handler) { + $metadata = $this->getMetadata($path); + $handler = ($metadata && $metadata['type'] === 'file') ? new File($this, $path) : new Directory($this, $path); + } + + $handler->setPath($path); + $handler->setFilesystem($this); + + return $handler; + } + + /** + * Assert a file is present. + * + * @param string $path path to file + * + * @throws FileNotFoundException + * + * @return void + */ + public function assertPresent($path) + { + if ($this->config->get('disable_asserts', false) === false && ! $this->has($path)) { + throw new FileNotFoundException($path); + } + } + + /** + * Assert a file is absent. + * + * @param string $path path to file + * + * @throws FileExistsException + * + * @return void + */ + public function assertAbsent($path) + { + if ($this->config->get('disable_asserts', false) === false && $this->has($path)) { + throw new FileExistsException($path); + } + } +} diff --git a/vendor/league/flysystem/src/FilesystemInterface.php b/vendor/league/flysystem/src/FilesystemInterface.php new file mode 100644 index 0000000..09b811b --- /dev/null +++ b/vendor/league/flysystem/src/FilesystemInterface.php @@ -0,0 +1,284 @@ +path = $path; + $this->filesystem = $filesystem; + } + + /** + * Check whether the entree is a directory. + * + * @return bool + */ + public function isDir() + { + return $this->getType() === 'dir'; + } + + /** + * Check whether the entree is a file. + * + * @return bool + */ + public function isFile() + { + return $this->getType() === 'file'; + } + + /** + * Retrieve the entree type (file|dir). + * + * @return string file or dir + */ + public function getType() + { + $metadata = $this->filesystem->getMetadata($this->path); + + return $metadata ? $metadata['type'] : 'dir'; + } + + /** + * Set the Filesystem object. + * + * @param FilesystemInterface $filesystem + * + * @return $this + */ + public function setFilesystem(FilesystemInterface $filesystem) + { + $this->filesystem = $filesystem; + + return $this; + } + + /** + * Retrieve the Filesystem object. + * + * @return FilesystemInterface + */ + public function getFilesystem() + { + return $this->filesystem; + } + + /** + * Set the entree path. + * + * @param string $path + * + * @return $this + */ + public function setPath($path) + { + $this->path = $path; + + return $this; + } + + /** + * Retrieve the entree path. + * + * @return string path + */ + public function getPath() + { + return $this->path; + } + + /** + * Plugins pass-through. + * + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, array $arguments) + { + array_unshift($arguments, $this->path); + $callback = [$this->filesystem, $method]; + + try { + return call_user_func_array($callback, $arguments); + } catch (BadMethodCallException $e) { + throw new BadMethodCallException( + 'Call to undefined method ' + . get_called_class() + . '::' . $method + ); + } + } +} diff --git a/vendor/league/flysystem/src/MountManager.php b/vendor/league/flysystem/src/MountManager.php new file mode 100644 index 0000000..620f540 --- /dev/null +++ b/vendor/league/flysystem/src/MountManager.php @@ -0,0 +1,648 @@ + Filesystem,] + * + * @throws InvalidArgumentException + */ + public function __construct(array $filesystems = []) + { + $this->mountFilesystems($filesystems); + } + + /** + * Mount filesystems. + * + * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,] + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function mountFilesystems(array $filesystems) + { + foreach ($filesystems as $prefix => $filesystem) { + $this->mountFilesystem($prefix, $filesystem); + } + + return $this; + } + + /** + * Mount filesystems. + * + * @param string $prefix + * @param FilesystemInterface $filesystem + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function mountFilesystem($prefix, FilesystemInterface $filesystem) + { + if ( ! is_string($prefix)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #1 to be a string.'); + } + + $this->filesystems[$prefix] = $filesystem; + + return $this; + } + + /** + * Get the filesystem with the corresponding prefix. + * + * @param string $prefix + * + * @throws FilesystemNotFoundException + * + * @return FilesystemInterface + */ + public function getFilesystem($prefix) + { + if ( ! isset($this->filesystems[$prefix])) { + throw new FilesystemNotFoundException('No filesystem mounted with prefix ' . $prefix); + } + + return $this->filesystems[$prefix]; + } + + /** + * Retrieve the prefix from an arguments array. + * + * @param array $arguments + * + * @throws InvalidArgumentException + * + * @return array [:prefix, :arguments] + */ + public function filterPrefix(array $arguments) + { + if (empty($arguments)) { + throw new InvalidArgumentException('At least one argument needed'); + } + + $path = array_shift($arguments); + + if ( ! is_string($path)) { + throw new InvalidArgumentException('First argument should be a string'); + } + + list($prefix, $path) = $this->getPrefixAndPath($path); + array_unshift($arguments, $path); + + return [$prefix, $arguments]; + } + + /** + * @param string $directory + * @param bool $recursive + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return array + */ + public function listContents($directory = '', $recursive = false) + { + list($prefix, $directory) = $this->getPrefixAndPath($directory); + $filesystem = $this->getFilesystem($prefix); + $result = $filesystem->listContents($directory, $recursive); + + foreach ($result as &$file) { + $file['filesystem'] = $prefix; + } + + return $result; + } + + /** + * Call forwarder. + * + * @param string $method + * @param array $arguments + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return mixed + */ + public function __call($method, $arguments) + { + list($prefix, $arguments) = $this->filterPrefix($arguments); + + return $this->invokePluginOnFilesystem($method, $arguments, $prefix); + } + + /** + * @param string $from + * @param string $to + * @param array $config + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * @throws FileExistsException + * + * @return bool + */ + public function copy($from, $to, array $config = []) + { + list($prefixFrom, $from) = $this->getPrefixAndPath($from); + + $buffer = $this->getFilesystem($prefixFrom)->readStream($from); + + if ($buffer === false) { + return false; + } + + list($prefixTo, $to) = $this->getPrefixAndPath($to); + + $result = $this->getFilesystem($prefixTo)->writeStream($to, $buffer, $config); + + if (is_resource($buffer)) { + fclose($buffer); + } + + return $result; + } + + /** + * List with plugin adapter. + * + * @param array $keys + * @param string $directory + * @param bool $recursive + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return array + */ + public function listWith(array $keys = [], $directory = '', $recursive = false) + { + list($prefix, $directory) = $this->getPrefixAndPath($directory); + $arguments = [$keys, $directory, $recursive]; + + return $this->invokePluginOnFilesystem('listWith', $arguments, $prefix); + } + + /** + * Move a file. + * + * @param string $from + * @param string $to + * @param array $config + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return bool + */ + public function move($from, $to, array $config = []) + { + list($prefixFrom, $pathFrom) = $this->getPrefixAndPath($from); + list($prefixTo, $pathTo) = $this->getPrefixAndPath($to); + + if ($prefixFrom === $prefixTo) { + $filesystem = $this->getFilesystem($prefixFrom); + $renamed = $filesystem->rename($pathFrom, $pathTo); + + if ($renamed && isset($config['visibility'])) { + return $filesystem->setVisibility($pathTo, $config['visibility']); + } + + return $renamed; + } + + $copied = $this->copy($from, $to, $config); + + if ($copied) { + return $this->delete($from); + } + + return false; + } + + /** + * Invoke a plugin on a filesystem mounted on a given prefix. + * + * @param string $method + * @param array $arguments + * @param string $prefix + * + * @throws FilesystemNotFoundException + * + * @return mixed + */ + public function invokePluginOnFilesystem($method, $arguments, $prefix) + { + $filesystem = $this->getFilesystem($prefix); + + try { + return $this->invokePlugin($method, $arguments, $filesystem); + } catch (PluginNotFoundException $e) { + // Let it pass, it's ok, don't panic. + } + + $callback = [$filesystem, $method]; + + return call_user_func_array($callback, $arguments); + } + + /** + * @param string $path + * + * @throws InvalidArgumentException + * + * @return string[] [:prefix, :path] + */ + protected function getPrefixAndPath($path) + { + if (strpos($path, '://') < 1) { + throw new InvalidArgumentException('No prefix detected in path: ' . $path); + } + + return explode('://', $path, 2); + } + + /** + * Check whether a file exists. + * + * @param string $path + * + * @return bool + */ + public function has($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->has($path); + } + + /** + * Read a file. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The file contents or false on failure. + */ + public function read($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->read($path); + } + + /** + * Retrieves a read-stream for a path. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return resource|false The path resource or false on failure. + */ + public function readStream($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->readStream($path); + } + + /** + * Get a file's metadata. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return array|false The file metadata or false on failure. + */ + public function getMetadata($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getMetadata($path); + } + + /** + * Get a file's size. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return int|false The file size or false on failure. + */ + public function getSize($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getSize($path); + } + + /** + * Get a file's mime-type. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The file mime-type or false on failure. + */ + public function getMimetype($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getMimetype($path); + } + + /** + * Get a file's timestamp. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The timestamp or false on failure. + */ + public function getTimestamp($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getTimestamp($path); + } + + /** + * Get a file's visibility. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The visibility (public|private) or false on failure. + */ + public function getVisibility($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getVisibility($path); + } + + /** + * Write a new file. + * + * @param string $path The path of the new file. + * @param string $contents The file contents. + * @param array $config An optional configuration array. + * + * @throws FileExistsException + * + * @return bool True on success, false on failure. + */ + public function write($path, $contents, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->write($path, $contents, $config); + } + + /** + * Write a new file using a stream. + * + * @param string $path The path of the new file. + * @param resource $resource The file handle. + * @param array $config An optional configuration array. + * + * @throws InvalidArgumentException If $resource is not a file handle. + * @throws FileExistsException + * + * @return bool True on success, false on failure. + */ + public function writeStream($path, $resource, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->writeStream($path, $resource, $config); + } + + /** + * Update an existing file. + * + * @param string $path The path of the existing file. + * @param string $contents The file contents. + * @param array $config An optional configuration array. + * + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function update($path, $contents, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->update($path, $contents, $config); + } + + /** + * Update an existing file using a stream. + * + * @param string $path The path of the existing file. + * @param resource $resource The file handle. + * @param array $config An optional configuration array. + * + * @throws InvalidArgumentException If $resource is not a file handle. + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function updateStream($path, $resource, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->updateStream($path, $resource, $config); + } + + /** + * Rename a file. + * + * @param string $path Path to the existing file. + * @param string $newpath The new path of the file. + * + * @throws FileExistsException Thrown if $newpath exists. + * @throws FileNotFoundException Thrown if $path does not exist. + * + * @return bool True on success, false on failure. + */ + public function rename($path, $newpath) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->rename($path, $newpath); + } + + /** + * Delete a file. + * + * @param string $path + * + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function delete($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->delete($path); + } + + /** + * Delete a directory. + * + * @param string $dirname + * + * @throws RootViolationException Thrown if $dirname is empty. + * + * @return bool True on success, false on failure. + */ + public function deleteDir($dirname) + { + list($prefix, $dirname) = $this->getPrefixAndPath($dirname); + + return $this->getFilesystem($prefix)->deleteDir($dirname); + } + + /** + * Create a directory. + * + * @param string $dirname The name of the new directory. + * @param array $config An optional configuration array. + * + * @return bool True on success, false on failure. + */ + public function createDir($dirname, array $config = []) + { + list($prefix, $dirname) = $this->getPrefixAndPath($dirname); + + return $this->getFilesystem($prefix)->createDir($dirname); + } + + /** + * Set the visibility for a file. + * + * @param string $path The path to the file. + * @param string $visibility One of 'public' or 'private'. + * + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function setVisibility($path, $visibility) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->setVisibility($path, $visibility); + } + + /** + * Create a file or update if exists. + * + * @param string $path The path to the file. + * @param string $contents The file contents. + * @param array $config An optional configuration array. + * + * @return bool True on success, false on failure. + */ + public function put($path, $contents, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->put($path, $contents, $config); + } + + /** + * Create a file or update if exists. + * + * @param string $path The path to the file. + * @param resource $resource The file handle. + * @param array $config An optional configuration array. + * + * @throws InvalidArgumentException Thrown if $resource is not a resource. + * + * @return bool True on success, false on failure. + */ + public function putStream($path, $resource, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->putStream($path, $resource, $config); + } + + /** + * Read and delete a file. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The file contents, or false on failure. + */ + public function readAndDelete($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->readAndDelete($path); + } + + /** + * Get a file/directory handler. + * + * @deprecated + * + * @param string $path The path to the file. + * @param Handler $handler An optional existing handler to populate. + * + * @return Handler Either a file or directory handler. + */ + public function get($path, Handler $handler = null) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->get($path); + } +} diff --git a/vendor/league/flysystem/src/NotSupportedException.php b/vendor/league/flysystem/src/NotSupportedException.php new file mode 100644 index 0000000..08f47f7 --- /dev/null +++ b/vendor/league/flysystem/src/NotSupportedException.php @@ -0,0 +1,37 @@ +getPathname()); + } + + /** + * Create a new exception for a link. + * + * @param string $systemType + * + * @return static + */ + public static function forFtpSystemType($systemType) + { + $message = "The FTP system type '$systemType' is currently not supported."; + + return new static($message); + } +} diff --git a/vendor/league/flysystem/src/Plugin/AbstractPlugin.php b/vendor/league/flysystem/src/Plugin/AbstractPlugin.php new file mode 100644 index 0000000..0d56789 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/AbstractPlugin.php @@ -0,0 +1,24 @@ +filesystem = $filesystem; + } +} diff --git a/vendor/league/flysystem/src/Plugin/EmptyDir.php b/vendor/league/flysystem/src/Plugin/EmptyDir.php new file mode 100644 index 0000000..b5ae7f5 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/EmptyDir.php @@ -0,0 +1,34 @@ +filesystem->listContents($dirname, false); + + foreach ($listing as $item) { + if ($item['type'] === 'dir') { + $this->filesystem->deleteDir($item['path']); + } else { + $this->filesystem->delete($item['path']); + } + } + } +} diff --git a/vendor/league/flysystem/src/Plugin/ForcedCopy.php b/vendor/league/flysystem/src/Plugin/ForcedCopy.php new file mode 100644 index 0000000..a41e9f3 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ForcedCopy.php @@ -0,0 +1,44 @@ +filesystem->delete($newpath); + } catch (FileNotFoundException $e) { + // The destination path does not exist. That's ok. + $deleted = true; + } + + if ($deleted) { + return $this->filesystem->copy($path, $newpath); + } + + return false; + } +} diff --git a/vendor/league/flysystem/src/Plugin/ForcedRename.php b/vendor/league/flysystem/src/Plugin/ForcedRename.php new file mode 100644 index 0000000..3f51cd6 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ForcedRename.php @@ -0,0 +1,44 @@ +filesystem->delete($newpath); + } catch (FileNotFoundException $e) { + // The destination path does not exist. That's ok. + $deleted = true; + } + + if ($deleted) { + return $this->filesystem->rename($path, $newpath); + } + + return false; + } +} diff --git a/vendor/league/flysystem/src/Plugin/GetWithMetadata.php b/vendor/league/flysystem/src/Plugin/GetWithMetadata.php new file mode 100644 index 0000000..6fe4f05 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/GetWithMetadata.php @@ -0,0 +1,51 @@ +filesystem->getMetadata($path); + + if ( ! $object) { + return false; + } + + $keys = array_diff($metadata, array_keys($object)); + + foreach ($keys as $key) { + if ( ! method_exists($this->filesystem, $method = 'get' . ucfirst($key))) { + throw new InvalidArgumentException('Could not fetch metadata: ' . $key); + } + + $object[$key] = $this->filesystem->{$method}($path); + } + + return $object; + } +} diff --git a/vendor/league/flysystem/src/Plugin/ListFiles.php b/vendor/league/flysystem/src/Plugin/ListFiles.php new file mode 100644 index 0000000..9669fe7 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ListFiles.php @@ -0,0 +1,35 @@ +filesystem->listContents($directory, $recursive); + + $filter = function ($object) { + return $object['type'] === 'file'; + }; + + return array_values(array_filter($contents, $filter)); + } +} diff --git a/vendor/league/flysystem/src/Plugin/ListPaths.php b/vendor/league/flysystem/src/Plugin/ListPaths.php new file mode 100644 index 0000000..514bdf0 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ListPaths.php @@ -0,0 +1,36 @@ +filesystem->listContents($directory, $recursive); + + foreach ($contents as $object) { + $result[] = $object['path']; + } + + return $result; + } +} diff --git a/vendor/league/flysystem/src/Plugin/ListWith.php b/vendor/league/flysystem/src/Plugin/ListWith.php new file mode 100644 index 0000000..d90464e --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ListWith.php @@ -0,0 +1,60 @@ +filesystem->listContents($directory, $recursive); + + foreach ($contents as $index => $object) { + if ($object['type'] === 'file') { + $missingKeys = array_diff($keys, array_keys($object)); + $contents[$index] = array_reduce($missingKeys, [$this, 'getMetadataByName'], $object); + } + } + + return $contents; + } + + /** + * Get a meta-data value by key name. + * + * @param array $object + * @param string $key + * + * @return array + */ + protected function getMetadataByName(array $object, $key) + { + $method = 'get' . ucfirst($key); + + if ( ! method_exists($this->filesystem, $method)) { + throw new \InvalidArgumentException('Could not get meta-data for key: ' . $key); + } + + $object[$key] = $this->filesystem->{$method}($object['path']); + + return $object; + } +} diff --git a/vendor/league/flysystem/src/Plugin/PluggableTrait.php b/vendor/league/flysystem/src/Plugin/PluggableTrait.php new file mode 100644 index 0000000..922edfe --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/PluggableTrait.php @@ -0,0 +1,97 @@ +plugins[$plugin->getMethod()] = $plugin; + + return $this; + } + + /** + * Find a specific plugin. + * + * @param string $method + * + * @throws PluginNotFoundException + * + * @return PluginInterface + */ + protected function findPlugin($method) + { + if ( ! isset($this->plugins[$method])) { + throw new PluginNotFoundException('Plugin not found for method: ' . $method); + } + + return $this->plugins[$method]; + } + + /** + * Invoke a plugin by method name. + * + * @param string $method + * @param array $arguments + * @param FilesystemInterface $filesystem + * + * @throws PluginNotFoundException + * + * @return mixed + */ + protected function invokePlugin($method, array $arguments, FilesystemInterface $filesystem) + { + $plugin = $this->findPlugin($method); + $plugin->setFilesystem($filesystem); + $callback = [$plugin, 'handle']; + + return call_user_func_array($callback, $arguments); + } + + /** + * Plugins pass-through. + * + * @param string $method + * @param array $arguments + * + * @throws BadMethodCallException + * + * @return mixed + */ + public function __call($method, array $arguments) + { + try { + return $this->invokePlugin($method, $arguments, $this); + } catch (PluginNotFoundException $e) { + throw new BadMethodCallException( + 'Call to undefined method ' + . get_class($this) + . '::' . $method + ); + } + } +} diff --git a/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php b/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php new file mode 100644 index 0000000..fd1d7e7 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php @@ -0,0 +1,10 @@ +hash = spl_object_hash($this); + static::$safeStorage[$this->hash] = []; + } + + public function storeSafely($key, $value) + { + static::$safeStorage[$this->hash][$key] = $value; + } + + public function retrieveSafely($key) + { + if (array_key_exists($key, static::$safeStorage[$this->hash])) { + return static::$safeStorage[$this->hash][$key]; + } + } + + public function __destruct() + { + unset(static::$safeStorage[$this->hash]); + } +} diff --git a/vendor/league/flysystem/src/UnreadableFileException.php b/vendor/league/flysystem/src/UnreadableFileException.php new file mode 100644 index 0000000..e668033 --- /dev/null +++ b/vendor/league/flysystem/src/UnreadableFileException.php @@ -0,0 +1,18 @@ +getRealPath() + ) + ); + } +} diff --git a/vendor/league/flysystem/src/Util.php b/vendor/league/flysystem/src/Util.php new file mode 100644 index 0000000..9de3777 --- /dev/null +++ b/vendor/league/flysystem/src/Util.php @@ -0,0 +1,349 @@ + '']; + } + + /** + * Normalize a dirname return value. + * + * @param string $dirname + * + * @return string normalized dirname + */ + public static function normalizeDirname($dirname) + { + return $dirname === '.' ? '' : $dirname; + } + + /** + * Get a normalized dirname from a path. + * + * @param string $path + * + * @return string dirname + */ + public static function dirname($path) + { + return static::normalizeDirname(dirname($path)); + } + + /** + * Map result arrays. + * + * @param array $object + * @param array $map + * + * @return array mapped result + */ + public static function map(array $object, array $map) + { + $result = []; + + foreach ($map as $from => $to) { + if ( ! isset($object[$from])) { + continue; + } + + $result[$to] = $object[$from]; + } + + return $result; + } + + /** + * Normalize path. + * + * @param string $path + * + * @throws LogicException + * + * @return string + */ + public static function normalizePath($path) + { + return static::normalizeRelativePath($path); + } + + /** + * Normalize relative directories in a path. + * + * @param string $path + * + * @throws LogicException + * + * @return string + */ + public static function normalizeRelativePath($path) + { + $path = str_replace('\\', '/', $path); + $path = static::removeFunkyWhiteSpace($path); + + $parts = []; + + foreach (explode('/', $path) as $part) { + switch ($part) { + case '': + case '.': + break; + + case '..': + if (empty($parts)) { + throw new LogicException( + 'Path is outside of the defined root, path: [' . $path . ']' + ); + } + array_pop($parts); + break; + + default: + $parts[] = $part; + break; + } + } + + return implode('/', $parts); + } + + /** + * Removes unprintable characters and invalid unicode characters. + * + * @param string $path + * + * @return string $path + */ + protected static function removeFunkyWhiteSpace($path) + { + // We do this check in a loop, since removing invalid unicode characters + // can lead to new characters being created. + while (preg_match('#\p{C}+|^\./#u', $path)) { + $path = preg_replace('#\p{C}+|^\./#u', '', $path); + } + + return $path; + } + + /** + * Normalize prefix. + * + * @param string $prefix + * @param string $separator + * + * @return string normalized path + */ + public static function normalizePrefix($prefix, $separator) + { + return rtrim($prefix, $separator) . $separator; + } + + /** + * Get content size. + * + * @param string $contents + * + * @return int content size + */ + public static function contentSize($contents) + { + return defined('MB_OVERLOAD_STRING') ? mb_strlen($contents, '8bit') : strlen($contents); + } + + /** + * Guess MIME Type based on the path of the file and it's content. + * + * @param string $path + * @param string|resource $content + * + * @return string|null MIME Type or NULL if no extension detected + */ + public static function guessMimeType($path, $content) + { + $mimeType = MimeType::detectByContent($content); + + if ( ! (empty($mimeType) || in_array($mimeType, ['application/x-empty', 'text/plain', 'text/x-asm']))) { + return $mimeType; + } + + return MimeType::detectByFilename($path); + } + + /** + * Emulate directories. + * + * @param array $listing + * + * @return array listing with emulated directories + */ + public static function emulateDirectories(array $listing) + { + $directories = []; + $listedDirectories = []; + + foreach ($listing as $object) { + list($directories, $listedDirectories) = static::emulateObjectDirectories($object, $directories, $listedDirectories); + } + + $directories = array_diff(array_unique($directories), array_unique($listedDirectories)); + + foreach ($directories as $directory) { + $listing[] = static::pathinfo($directory) + ['type' => 'dir']; + } + + return $listing; + } + + /** + * Ensure a Config instance. + * + * @param null|array|Config $config + * + * @return Config config instance + * + * @throw LogicException + */ + public static function ensureConfig($config) + { + if ($config === null) { + return new Config(); + } + + if ($config instanceof Config) { + return $config; + } + + if (is_array($config)) { + return new Config($config); + } + + throw new LogicException('A config should either be an array or a Flysystem\Config object.'); + } + + /** + * Rewind a stream. + * + * @param resource $resource + */ + public static function rewindStream($resource) + { + if (ftell($resource) !== 0 && static::isSeekableStream($resource)) { + rewind($resource); + } + } + + public static function isSeekableStream($resource) + { + $metadata = stream_get_meta_data($resource); + + return $metadata['seekable']; + } + + /** + * Get the size of a stream. + * + * @param resource $resource + * + * @return int stream size + */ + public static function getStreamSize($resource) + { + $stat = fstat($resource); + + return $stat['size']; + } + + /** + * Emulate the directories of a single object. + * + * @param array $object + * @param array $directories + * @param array $listedDirectories + * + * @return array + */ + protected static function emulateObjectDirectories(array $object, array $directories, array $listedDirectories) + { + if ($object['type'] === 'dir') { + $listedDirectories[] = $object['path']; + } + + if ( ! isset($object['dirname']) || trim($object['dirname']) === '') { + return [$directories, $listedDirectories]; + } + + $parent = $object['dirname']; + + while (isset($parent) && trim($parent) !== '' && ! in_array($parent, $directories)) { + $directories[] = $parent; + $parent = static::dirname($parent); + } + + if (isset($object['type']) && $object['type'] === 'dir') { + $listedDirectories[] = $object['path']; + + return [$directories, $listedDirectories]; + } + + return [$directories, $listedDirectories]; + } + + /** + * Returns the trailing name component of the path. + * + * @param string $path + * + * @return string + */ + private static function basename($path) + { + $separators = DIRECTORY_SEPARATOR === '/' ? '/' : '\/'; + + $path = rtrim($path, $separators); + + $basename = preg_replace('#.*?([^' . preg_quote($separators, '#') . ']+$)#', '$1', $path); + + if (DIRECTORY_SEPARATOR === '/') { + return $basename; + } + // @codeCoverageIgnoreStart + // Extra Windows path munging. This is tested via AppVeyor, but code + // coverage is not reported. + + // Handle relative paths with drive letters. c:file.txt. + while (preg_match('#^[a-zA-Z]{1}:[^\\\/]#', $basename)) { + $basename = substr($basename, 2); + } + + // Remove colon for standalone drive letter names. + if (preg_match('#^[a-zA-Z]{1}:$#', $basename)) { + $basename = rtrim($basename, ':'); + } + + return $basename; + // @codeCoverageIgnoreEnd + } +} diff --git a/vendor/league/flysystem/src/Util/ContentListingFormatter.php b/vendor/league/flysystem/src/Util/ContentListingFormatter.php new file mode 100644 index 0000000..ae0d3b9 --- /dev/null +++ b/vendor/league/flysystem/src/Util/ContentListingFormatter.php @@ -0,0 +1,122 @@ +directory = rtrim($directory, '/'); + $this->recursive = $recursive; + $this->caseSensitive = $caseSensitive; + } + + /** + * Format contents listing. + * + * @param array $listing + * + * @return array + */ + public function formatListing(array $listing) + { + $listing = array_filter(array_map([$this, 'addPathInfo'], $listing), [$this, 'isEntryOutOfScope']); + + return $this->sortListing(array_values($listing)); + } + + private function addPathInfo(array $entry) + { + return $entry + Util::pathinfo($entry['path']); + } + + /** + * Determine if the entry is out of scope. + * + * @param array $entry + * + * @return bool + */ + private function isEntryOutOfScope(array $entry) + { + if (empty($entry['path']) && $entry['path'] !== '0') { + return false; + } + + if ($this->recursive) { + return $this->residesInDirectory($entry); + } + + return $this->isDirectChild($entry); + } + + /** + * Check if the entry resides within the parent directory. + * + * @param array $entry + * + * @return bool + */ + private function residesInDirectory(array $entry) + { + if ($this->directory === '') { + return true; + } + + return $this->caseSensitive + ? strpos($entry['path'], $this->directory . '/') === 0 + : stripos($entry['path'], $this->directory . '/') === 0; + } + + /** + * Check if the entry is a direct child of the directory. + * + * @param array $entry + * + * @return bool + */ + private function isDirectChild(array $entry) + { + return $this->caseSensitive + ? $entry['dirname'] === $this->directory + : strcasecmp($this->directory, $entry['dirname']) === 0; + } + + /** + * @param array $listing + * + * @return array + */ + private function sortListing(array $listing) + { + usort($listing, function ($a, $b) { + return strcasecmp($a['path'], $b['path']); + }); + + return $listing; + } +} diff --git a/vendor/league/flysystem/src/Util/MimeType.php b/vendor/league/flysystem/src/Util/MimeType.php new file mode 100644 index 0000000..02f67ac --- /dev/null +++ b/vendor/league/flysystem/src/Util/MimeType.php @@ -0,0 +1,245 @@ + 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'csv' => 'text/csv', + 'bin' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'class' => 'application/octet-stream', + 'psd' => 'application/x-photoshop', + 'so' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/pdf', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'xlt' => 'application/vnd.ms-excel', + 'xla' => 'application/vnd.ms-excel', + 'ppt' => 'application/powerpoint', + 'pot' => 'application/vnd.ms-powerpoint', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppa' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'wbxml' => 'application/wbxml', + 'wmlc' => 'application/wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'gz' => 'application/x-gzip', + 'gzip' => 'application/x-gzip', + 'php' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => 'application/javascript', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'z' => 'application/x-compress', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'rdf' => 'application/rdf+xml', + 'zip' => 'application/x-zip', + 'rar' => 'application/x-rar', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'rv' => 'video/vnd.rn-realvideo', + 'wav' => 'audio/x-wav', + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'png' => 'image/png', + 'gif' => 'image/gif', + 'bmp' => 'image/bmp', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'svg' => 'image/svg+xml', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'txt' => 'text/plain', + 'text' => 'text/plain', + 'log' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'xml' => 'application/xml', + 'xsl' => 'application/xml', + 'dmn' => 'application/octet-stream', + 'bpmn' => 'application/octet-stream', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dot' => 'application/msword', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'word' => 'application/msword', + 'xl' => 'application/excel', + 'eml' => 'message/rfc822', + 'json' => 'application/json', + 'pem' => 'application/x-x509-user-cert', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'p7a' => 'application/x-pkcs7-signature', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'crt' => 'application/x-x509-ca-cert', + 'crl' => 'application/pkix-crl', + 'der' => 'application/x-x509-ca-cert', + 'kdb' => 'application/octet-stream', + 'pgp' => 'application/pgp', + 'gpg' => 'application/gpg-keys', + 'sst' => 'application/octet-stream', + 'csr' => 'application/octet-stream', + 'rsa' => 'application/x-pkcs7', + 'cer' => 'application/pkix-cert', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gp', + 'mp4' => 'video/mp4', + 'm4a' => 'audio/x-m4a', + 'f4v' => 'video/mp4', + 'webm' => 'video/webm', + 'aac' => 'audio/x-acc', + 'm4u' => 'application/vnd.mpegurl', + 'm3u' => 'text/plain', + 'xspf' => 'application/xspf+xml', + 'vlc' => 'application/videolan', + 'wmv' => 'video/x-ms-wmv', + 'au' => 'audio/x-au', + 'ac3' => 'audio/ac3', + 'flac' => 'audio/x-flac', + 'ogg' => 'audio/ogg', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'ics' => 'text/calendar', + 'zsh' => 'text/x-scriptzsh', + '7zip' => 'application/x-7z-compressed', + 'cdr' => 'application/cdr', + 'wma' => 'audio/x-ms-wma', + 'jar' => 'application/java-archive', + 'tex' => 'application/x-tex', + 'latex' => 'application/x-latex', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + ]; + + /** + * Detects MIME Type based on given content. + * + * @param mixed $content + * + * @return string|null MIME Type or NULL if no mime type detected + */ + public static function detectByContent($content) + { + if ( ! class_exists('finfo') || ! is_string($content)) { + return null; + } + try { + $finfo = new finfo(FILEINFO_MIME_TYPE); + + return $finfo->buffer($content) ?: null; + // @codeCoverageIgnoreStart + } catch (ErrorException $e) { + // This is caused by an array to string conversion error. + } + } // @codeCoverageIgnoreEnd + + /** + * Detects MIME Type based on file extension. + * + * @param string $extension + * + * @return string|null MIME Type or NULL if no extension detected + */ + public static function detectByFileExtension($extension) + { + return isset(static::$extensionToMimeTypeMap[$extension]) + ? static::$extensionToMimeTypeMap[$extension] + : 'text/plain'; + } + + /** + * @param string $filename + * + * @return string|null MIME Type or NULL if no extension detected + */ + public static function detectByFilename($filename) + { + $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + + return empty($extension) ? 'text/plain' : static::detectByFileExtension($extension); + } + + /** + * @return array Map of file extension to MIME Type + */ + public static function getExtensionToMimeTypeMap() + { + return static::$extensionToMimeTypeMap; + } +} diff --git a/vendor/league/flysystem/src/Util/StreamHasher.php b/vendor/league/flysystem/src/Util/StreamHasher.php new file mode 100644 index 0000000..938ec5d --- /dev/null +++ b/vendor/league/flysystem/src/Util/StreamHasher.php @@ -0,0 +1,36 @@ +algo = $algo; + } + + /** + * @param resource $resource + * + * @return string + */ + public function hash($resource) + { + rewind($resource); + $context = hash_init($this->algo); + hash_update_stream($context, $resource); + fclose($resource); + + return hash_final($context); + } +} diff --git a/vendor/mtdowling/jmespath.php/.gitignore b/vendor/mtdowling/jmespath.php/.gitignore new file mode 100644 index 0000000..34a9206 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/.gitignore @@ -0,0 +1,5 @@ +vendor +composer.lock +phpunit.xml +compiled +artifacts/ diff --git a/vendor/mtdowling/jmespath.php/.travis.yml b/vendor/mtdowling/jmespath.php/.travis.yml new file mode 100644 index 0000000..686e7ff --- /dev/null +++ b/vendor/mtdowling/jmespath.php/.travis.yml @@ -0,0 +1,31 @@ +language: php + +matrix: + include: + - php: 5.4 + dist: trusty + - php: 5.5 + dist: trusty + - php: 5.6 + dist: trusty + - php: 7.0 + dist: trusty + - php: 7.1 + dist: bionic + - php: 7.2 + dist: bionic + - php: 7.3 + dist: bionic + - php: 7.4 + dist: bionic + - php: hhvm-3.18 + dist: trusty + +install: travis_retry composer install + +script: make test + +after_script: + - make perf + - JP_PHP_COMPILE=on make perf + - JP_PHP_COMPILE=on CACHE=on make perf diff --git a/vendor/mtdowling/jmespath.php/CHANGELOG.md b/vendor/mtdowling/jmespath.php/CHANGELOG.md new file mode 100644 index 0000000..0f94acb --- /dev/null +++ b/vendor/mtdowling/jmespath.php/CHANGELOG.md @@ -0,0 +1,58 @@ +# CHANGELOG + +## 2.5.0 - 2019-12-30 + +* Full support for PHP 7.0-7.4. +* Fixed autoloading when run from within vendor folder. +* Full multibyte (UTF-8) string support. + +## 2.4.0 - 2016-12-03 + +* Added support for floats when interpreting data. +* Added a function_exists check to work around redeclaration issues. + +## 2.3.0 - 2016-01-05 + +* Added support for [JEP-9](https://github.com/jmespath/jmespath.site/blob/master/docs/proposals/improved-filters.rst), + including unary filter expressions, and `&&` filter expressions. +* Fixed various parsing issues, including not removing escaped single quotes + from raw string literals. +* Added support for the `map` function. +* Fixed several issues with code generation. + +## 2.2.0 - 2015-05-27 + +* Added support for [JEP-12](https://github.com/jmespath/jmespath.site/blob/master/docs/proposals/raw-string-literals.rst) + and raw string literals (e.g., `'foo'`). + +## 2.1.0 - 2014-01-13 + +* Added `JmesPath\Env::cleanCompileDir()` to delete any previously compiled + JMESPath expressions. + +## 2.0.0 - 2014-01-11 + +* Moving to a flattened namespace structure. +* Runtimes are now only PHP callables. +* Fixed an error in the way empty JSON literals are parsed so that they now + return an empty string to match the Python and JavaScript implementations. +* Removed functions from runtimes. Instead there is now a function dispatcher + class, FnDispatcher, that provides function implementations behind a single + dispatch function. +* Removed ExprNode in lieu of just using a PHP callable with bound variables. +* Removed debug methods from runtimes and instead into a new Debugger class. +* Heavily cleaned up function argument validation. +* Slice syntax is now properly validated (i.e., colons are followed by the + appropriate value). +* Lots of code cleanup and performance improvements. +* Added a convenient `JmesPath\search()` function. +* **IMPORTANT**: Relocating the project to https://github.com/jmespath/jmespath.php + +## 1.1.1 - 2014-10-08 + +* Added support for using ArrayAccess and Countable as arrays and objects. + +## 1.1.0 - 2014-08-06 + +* Added the ability to search data returned from json_decode() where JSON + objects are returned as stdClass objects. diff --git a/vendor/mtdowling/jmespath.php/LICENSE b/vendor/mtdowling/jmespath.php/LICENSE new file mode 100644 index 0000000..5c970a4 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling + +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/mtdowling/jmespath.php/Makefile b/vendor/mtdowling/jmespath.php/Makefile new file mode 100644 index 0000000..9677269 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/Makefile @@ -0,0 +1,19 @@ +all: clean coverage + +test: + vendor/bin/phpunit + +coverage: + vendor/bin/phpunit --coverage-html=artifacts/coverage + +view-coverage: + open artifacts/coverage/index.html + +clean: + rm -rf artifacts/* + rm -rf compiled/* + +perf: + php bin/perf.php + +.PHONY: test coverage perf diff --git a/vendor/mtdowling/jmespath.php/README.rst b/vendor/mtdowling/jmespath.php/README.rst new file mode 100644 index 0000000..b65ee46 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/README.rst @@ -0,0 +1,123 @@ +============ +jmespath.php +============ + +JMESPath (pronounced "jaymz path") allows you to declaratively specify how to +extract elements from a JSON document. *jmespath.php* allows you to use +JMESPath in PHP applications with PHP data structures. It requires PHP 5.4 or +greater and can be installed through `Composer `_ +using the ``mtdowling/jmespath.php`` package. + +.. code-block:: php + + require 'vendor/autoload.php'; + + $expression = 'foo.*.baz'; + + $data = [ + 'foo' => [ + 'bar' => ['baz' => 1], + 'bam' => ['baz' => 2], + 'boo' => ['baz' => 3] + ] + ]; + + JmesPath\search($expression, $data); + // Returns: [1, 2, 3] + +- `JMESPath Tutorial `_ +- `JMESPath Grammar `_ +- `JMESPath Python library `_ + +PHP Usage +========= + +The ``JmesPath\search`` function can be used in most cases when using the +library. This function utilizes a JMESPath runtime based on your environment. +The runtime utilized can be configured using environment variables and may at +some point in the future automatically utilize a C extension if available. + +.. code-block:: php + + $result = JmesPath\search($expression, $data); + + // or, if you require PSR-4 compliance. + $result = JmesPath\Env::search($expression, $data); + +Runtimes +-------- + +jmespath.php utilizes *runtimes*. There are currently two runtimes: +AstRuntime and CompilerRuntime. + +AstRuntime is utilized by ``JmesPath\search()`` and ``JmesPath\Env::search()`` +by default. + +AstRuntime +~~~~~~~~~~ + +The AstRuntime will parse an expression, cache the resulting AST in memory, +and interpret the AST using an external tree visitor. AstRuntime provides a +good general approach for interpreting JMESPath expressions that have a low to +moderate level of reuse. + +.. code-block:: php + + $runtime = new JmesPath\AstRuntime(); + $runtime('foo.bar', ['foo' => ['bar' => 'baz']]); + // > 'baz' + +CompilerRuntime +~~~~~~~~~~~~~~~ + +``JmesPath\CompilerRuntime`` provides the most performance for +applications that have a moderate to high level of reuse of JMESPath +expressions. The CompilerRuntime will walk a JMESPath AST and emit PHP source +code, resulting in anywhere from 7x to 60x speed improvements. + +Compiling JMESPath expressions to source code is a slower process than just +walking and interpreting a JMESPath AST (via the AstRuntime). However, +running the compiled JMESPath code results in much better performance than +walking an AST. This essentially means that there is a warm-up period when +using the ``CompilerRuntime``, but after the warm-up period, it will provide +much better performance. + +Use the CompilerRuntime if you know that you will be executing JMESPath +expressions more than once or if you can pre-compile JMESPath expressions +before executing them (for example, server-side applications). + +.. code-block:: php + + // Note: The cache directory argument is optional. + $runtime = new JmesPath\CompilerRuntime('/path/to/compile/folder'); + $runtime('foo.bar', ['foo' => ['bar' => 'baz']]); + // > 'baz' + +Environment Variables +^^^^^^^^^^^^^^^^^^^^^ + +You can utilize the CompilerRuntime in ``JmesPath\search()`` by setting +the ``JP_PHP_COMPILE`` environment variable to "on" or to a directory +on disk used to store cached expressions. + +Testing +======= + +A comprehensive list of test cases can be found at +https://github.com/jmespath/jmespath.php/tree/master/tests/compliance. +These compliance tests are utilized by jmespath.php to ensure consistency with +other implementations, and can serve as examples of the language. + +jmespath.php is tested using PHPUnit. In order to run the tests, you need to +first install the dependencies using Composer as described in the *Installation* +section. Next you just need to run the tests via make: + +.. code-block:: bash + + make test + +You can run a suite of performance tests as well: + +.. code-block:: bash + + make perf diff --git a/vendor/mtdowling/jmespath.php/bin/jp.php b/vendor/mtdowling/jmespath.php/bin/jp.php new file mode 100644 index 0000000..c8433b5 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/bin/jp.php @@ -0,0 +1,74 @@ +#!/usr/bin/env php +check(); +unset($xdebug); + +$dir = isset($argv[1]) ? $argv[1] : __DIR__ . '/../tests/compliance/perf'; +is_dir($dir) or die('Dir not found: ' . $dir); +// Warm up the runner +\JmesPath\Env::search('foo', []); + +$total = 0; +foreach (glob($dir . '/*.json') as $file) { + $total += runSuite($file); +} +echo "\nTotal time: {$total}\n"; + +function runSuite($file) +{ + $contents = file_get_contents($file); + $json = json_decode($contents, true); + $total = 0; + foreach ($json as $suite) { + foreach ($suite['cases'] as $case) { + $total += runCase( + $suite['given'], + $case['expression'], + $case['name'] + ); + } + } + return $total; +} + +function runCase($given, $expression, $name) +{ + $best = 99999; + $runtime = \JmesPath\Env::createRuntime(); + + for ($i = 0; $i < 100; $i++) { + $t = microtime(true); + $runtime($expression, $given); + $tryTime = (microtime(true) - $t) * 1000; + if ($tryTime < $best) { + $best = $tryTime; + } + if (!getenv('CACHE')) { + $runtime = \JmesPath\Env::createRuntime(); + // Delete compiled scripts if not caching. + if ($runtime instanceof \JmesPath\CompilerRuntime) { + array_map('unlink', glob(sys_get_temp_dir() . '/jmespath_*.php')); + } + } + } + + printf("time: %07.4fms name: %s\n", $best, $name); + + return $best; +} diff --git a/vendor/mtdowling/jmespath.php/composer.json b/vendor/mtdowling/jmespath.php/composer.json new file mode 100644 index 0000000..8ae5bb0 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/composer.json @@ -0,0 +1,39 @@ +{ + "name": "mtdowling/jmespath.php", + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": ["json", "jsonpath"], + "license": "MIT", + + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + + "require": { + "php": ">=5.4.0", + "symfony/polyfill-mbstring": "^1.4" + }, + + "require-dev": { + "composer/xdebug-handler": "^1.2", + "phpunit/phpunit": "^4.8.36|^7.5.15" + }, + + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": ["src/JmesPath.php"] + }, + + "bin": ["bin/jp.php"], + + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + } +} diff --git a/vendor/mtdowling/jmespath.php/phpunit.xml.dist b/vendor/mtdowling/jmespath.php/phpunit.xml.dist new file mode 100644 index 0000000..0e48023 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + + + + tests + + + + + + src + + + + diff --git a/vendor/mtdowling/jmespath.php/src/AstRuntime.php b/vendor/mtdowling/jmespath.php/src/AstRuntime.php new file mode 100644 index 0000000..db8a60e --- /dev/null +++ b/vendor/mtdowling/jmespath.php/src/AstRuntime.php @@ -0,0 +1,47 @@ +interpreter = new TreeInterpreter($fnDispatcher); + $this->parser = $parser ?: new Parser(); + } + + /** + * Returns data from the provided input that matches a given JMESPath + * expression. + * + * @param string $expression JMESPath expression to evaluate + * @param mixed $data Data to search. This data should be data that + * is similar to data returned from json_decode + * using associative arrays rather than objects. + * + * @return mixed|null Returns the matching data or null + */ + public function __invoke($expression, $data) + { + if (!isset($this->cache[$expression])) { + // Clear the AST cache when it hits 1024 entries + if (++$this->cachedCount > 1024) { + $this->cache = []; + $this->cachedCount = 0; + } + $this->cache[$expression] = $this->parser->parse($expression); + } + + return $this->interpreter->visit($this->cache[$expression], $data); + } +} diff --git a/vendor/mtdowling/jmespath.php/src/CompilerRuntime.php b/vendor/mtdowling/jmespath.php/src/CompilerRuntime.php new file mode 100644 index 0000000..f2becb9 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/src/CompilerRuntime.php @@ -0,0 +1,83 @@ +parser = $parser ?: new Parser(); + $this->compiler = new TreeCompiler(); + $dir = $dir ?: sys_get_temp_dir(); + + if (!is_dir($dir) && !mkdir($dir, 0755, true)) { + throw new \RuntimeException("Unable to create cache directory: $dir"); + } + + $this->cacheDir = realpath($dir); + $this->interpreter = new TreeInterpreter(); + } + + /** + * Returns data from the provided input that matches a given JMESPath + * expression. + * + * @param string $expression JMESPath expression to evaluate + * @param mixed $data Data to search. This data should be data that + * is similar to data returned from json_decode + * using associative arrays rather than objects. + * + * @return mixed|null Returns the matching data or null + * @throws \RuntimeException + */ + public function __invoke($expression, $data) + { + $functionName = 'jmespath_' . md5($expression); + + if (!function_exists($functionName)) { + $filename = "{$this->cacheDir}/{$functionName}.php"; + if (!file_exists($filename)) { + $this->compile($filename, $expression, $functionName); + } + require $filename; + } + + return $functionName($this->interpreter, $data); + } + + private function compile($filename, $expression, $functionName) + { + $code = $this->compiler->visit( + $this->parser->parse($expression), + $functionName, + $expression + ); + + if (!file_put_contents($filename, $code)) { + throw new \RuntimeException(sprintf( + 'Unable to write the compiled PHP code to: %s (%s)', + $filename, + var_export(error_get_last(), true) + )); + } + } +} diff --git a/vendor/mtdowling/jmespath.php/src/DebugRuntime.php b/vendor/mtdowling/jmespath.php/src/DebugRuntime.php new file mode 100644 index 0000000..4052561 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/src/DebugRuntime.php @@ -0,0 +1,109 @@ +runtime = $runtime; + $this->out = $output ?: STDOUT; + $this->lexer = new Lexer(); + $this->parser = new Parser($this->lexer); + } + + public function __invoke($expression, $data) + { + if ($this->runtime instanceof CompilerRuntime) { + return $this->debugCompiled($expression, $data); + } + + return $this->debugInterpreted($expression, $data); + } + + private function debugInterpreted($expression, $data) + { + return $this->debugCallback( + function () use ($expression, $data) { + $runtime = $this->runtime; + return $runtime($expression, $data); + }, + $expression, + $data + ); + } + + private function debugCompiled($expression, $data) + { + $result = $this->debugCallback( + function () use ($expression, $data) { + $runtime = $this->runtime; + return $runtime($expression, $data); + }, + $expression, + $data + ); + $this->dumpCompiledCode($expression); + + return $result; + } + + private function dumpTokens($expression) + { + $lexer = new Lexer(); + fwrite($this->out, "Tokens\n======\n\n"); + $tokens = $lexer->tokenize($expression); + + foreach ($tokens as $t) { + fprintf( + $this->out, + "%3d %-13s %s\n", $t['pos'], $t['type'], + json_encode($t['value']) + ); + } + + fwrite($this->out, "\n"); + } + + private function dumpAst($expression) + { + $parser = new Parser(); + $ast = $parser->parse($expression); + fwrite($this->out, "AST\n========\n\n"); + fwrite($this->out, json_encode($ast, JSON_PRETTY_PRINT) . "\n"); + } + + private function dumpCompiledCode($expression) + { + fwrite($this->out, "Code\n========\n\n"); + $dir = sys_get_temp_dir(); + $hash = md5($expression); + $functionName = "jmespath_{$hash}"; + $filename = "{$dir}/{$functionName}.php"; + fwrite($this->out, "File: {$filename}\n\n"); + fprintf($this->out, file_get_contents($filename)); + } + + private function debugCallback(callable $debugFn, $expression, $data) + { + fprintf($this->out, "Expression\n==========\n\n%s\n\n", $expression); + $this->dumpTokens($expression); + $this->dumpAst($expression); + fprintf($this->out, "\nData\n====\n\n%s\n\n", json_encode($data, JSON_PRETTY_PRINT)); + $startTime = microtime(true); + $result = $debugFn(); + $total = microtime(true) - $startTime; + fprintf($this->out, "\nResult\n======\n\n%s\n\n", json_encode($result, JSON_PRETTY_PRINT)); + fwrite($this->out, "Time\n====\n\n"); + fprintf($this->out, "Total time: %f ms\n\n", $total); + + return $result; + } +} diff --git a/vendor/mtdowling/jmespath.php/src/Env.php b/vendor/mtdowling/jmespath.php/src/Env.php new file mode 100644 index 0000000..9472d72 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/src/Env.php @@ -0,0 +1,91 @@ +{'fn_' . $fn}($args); + } + + private function fn_abs(array $args) + { + $this->validate('abs', $args, [['number']]); + return abs($args[0]); + } + + private function fn_avg(array $args) + { + $this->validate('avg', $args, [['array']]); + $sum = $this->reduce('avg:0', $args[0], ['number'], function ($a, $b) { + return $a + $b; + }); + return $args[0] ? ($sum / count($args[0])) : null; + } + + private function fn_ceil(array $args) + { + $this->validate('ceil', $args, [['number']]); + return ceil($args[0]); + } + + private function fn_contains(array $args) + { + $this->validate('contains', $args, [['string', 'array'], ['any']]); + if (is_array($args[0])) { + return in_array($args[1], $args[0]); + } elseif (is_string($args[1])) { + return mb_strpos($args[0], $args[1], 0, 'UTF-8') !== false; + } else { + return null; + } + } + + private function fn_ends_with(array $args) + { + $this->validate('ends_with', $args, [['string'], ['string']]); + list($search, $suffix) = $args; + return $suffix === '' || mb_substr($search, -mb_strlen($suffix, 'UTF-8'), null, 'UTF-8') === $suffix; + } + + private function fn_floor(array $args) + { + $this->validate('floor', $args, [['number']]); + return floor($args[0]); + } + + private function fn_not_null(array $args) + { + if (!$args) { + throw new \RuntimeException( + "not_null() expects 1 or more arguments, 0 were provided" + ); + } + + return array_reduce($args, function ($carry, $item) { + return $carry !== null ? $carry : $item; + }); + } + + private function fn_join(array $args) + { + $this->validate('join', $args, [['string'], ['array']]); + $fn = function ($a, $b, $i) use ($args) { + return $i ? ($a . $args[0] . $b) : $b; + }; + return $this->reduce('join:0', $args[1], ['string'], $fn); + } + + private function fn_keys(array $args) + { + $this->validate('keys', $args, [['object']]); + return array_keys((array) $args[0]); + } + + private function fn_length(array $args) + { + $this->validate('length', $args, [['string', 'array', 'object']]); + return is_string($args[0]) ? mb_strlen($args[0], 'UTF-8') : count((array) $args[0]); + } + + private function fn_max(array $args) + { + $this->validate('max', $args, [['array']]); + $fn = function ($a, $b) { + return $a >= $b ? $a : $b; + }; + return $this->reduce('max:0', $args[0], ['number', 'string'], $fn); + } + + private function fn_max_by(array $args) + { + $this->validate('max_by', $args, [['array'], ['expression']]); + $expr = $this->wrapExpression('max_by:1', $args[1], ['number', 'string']); + $fn = function ($carry, $item, $index) use ($expr) { + return $index + ? ($expr($carry) >= $expr($item) ? $carry : $item) + : $item; + }; + return $this->reduce('max_by:1', $args[0], ['any'], $fn); + } + + private function fn_min(array $args) + { + $this->validate('min', $args, [['array']]); + $fn = function ($a, $b, $i) { + return $i && $a <= $b ? $a : $b; + }; + return $this->reduce('min:0', $args[0], ['number', 'string'], $fn); + } + + private function fn_min_by(array $args) + { + $this->validate('min_by', $args, [['array'], ['expression']]); + $expr = $this->wrapExpression('min_by:1', $args[1], ['number', 'string']); + $i = -1; + $fn = function ($a, $b) use ($expr, &$i) { + return ++$i ? ($expr($a) <= $expr($b) ? $a : $b) : $b; + }; + return $this->reduce('min_by:1', $args[0], ['any'], $fn); + } + + private function fn_reverse(array $args) + { + $this->validate('reverse', $args, [['array', 'string']]); + if (is_array($args[0])) { + return array_reverse($args[0]); + } elseif (is_string($args[0])) { + return strrev($args[0]); + } else { + throw new \RuntimeException('Cannot reverse provided argument'); + } + } + + private function fn_sum(array $args) + { + $this->validate('sum', $args, [['array']]); + $fn = function ($a, $b) { + return $a + $b; + }; + return $this->reduce('sum:0', $args[0], ['number'], $fn); + } + + private function fn_sort(array $args) + { + $this->validate('sort', $args, [['array']]); + $valid = ['string', 'number']; + return Utils::stableSort($args[0], function ($a, $b) use ($valid) { + $this->validateSeq('sort:0', $valid, $a, $b); + return strnatcmp($a, $b); + }); + } + + private function fn_sort_by(array $args) + { + $this->validate('sort_by', $args, [['array'], ['expression']]); + $expr = $args[1]; + $valid = ['string', 'number']; + return Utils::stableSort( + $args[0], + function ($a, $b) use ($expr, $valid) { + $va = $expr($a); + $vb = $expr($b); + $this->validateSeq('sort_by:0', $valid, $va, $vb); + return strnatcmp($va, $vb); + } + ); + } + + private function fn_starts_with(array $args) + { + $this->validate('starts_with', $args, [['string'], ['string']]); + list($search, $prefix) = $args; + return $prefix === '' || mb_strpos($search, $prefix, 0, 'UTF-8') === 0; + } + + private function fn_type(array $args) + { + $this->validateArity('type', count($args), 1); + return Utils::type($args[0]); + } + + private function fn_to_string(array $args) + { + $this->validateArity('to_string', count($args), 1); + $v = $args[0]; + if (is_string($v)) { + return $v; + } elseif (is_object($v) + && !($v instanceof \JsonSerializable) + && method_exists($v, '__toString') + ) { + return (string) $v; + } + + return json_encode($v); + } + + private function fn_to_number(array $args) + { + $this->validateArity('to_number', count($args), 1); + $value = $args[0]; + $type = Utils::type($value); + if ($type == 'number') { + return $value; + } elseif ($type == 'string' && is_numeric($value)) { + return mb_strpos($value, '.', 0, 'UTF-8') ? (float) $value : (int) $value; + } else { + return null; + } + } + + private function fn_values(array $args) + { + $this->validate('values', $args, [['array', 'object']]); + return array_values((array) $args[0]); + } + + private function fn_merge(array $args) + { + if (!$args) { + throw new \RuntimeException( + "merge() expects 1 or more arguments, 0 were provided" + ); + } + + return call_user_func_array('array_replace', $args); + } + + private function fn_to_array(array $args) + { + $this->validate('to_array', $args, [['any']]); + + return Utils::isArray($args[0]) ? $args[0] : [$args[0]]; + } + + private function fn_map(array $args) + { + $this->validate('map', $args, [['expression'], ['any']]); + $result = []; + foreach ($args[1] as $a) { + $result[] = $args[0]($a); + } + return $result; + } + + private function typeError($from, $msg) + { + if (mb_strpos($from, ':', 0, 'UTF-8')) { + list($fn, $pos) = explode(':', $from); + throw new \RuntimeException( + sprintf('Argument %d of %s %s', $pos, $fn, $msg) + ); + } else { + throw new \RuntimeException( + sprintf('Type error: %s %s', $from, $msg) + ); + } + } + + private function validateArity($from, $given, $expected) + { + if ($given != $expected) { + $err = "%s() expects {$expected} arguments, {$given} were provided"; + throw new \RuntimeException(sprintf($err, $from)); + } + } + + private function validate($from, $args, $types = []) + { + $this->validateArity($from, count($args), count($types)); + foreach ($args as $index => $value) { + if (!isset($types[$index]) || !$types[$index]) { + continue; + } + $this->validateType("{$from}:{$index}", $value, $types[$index]); + } + } + + private function validateType($from, $value, array $types) + { + if ($types[0] == 'any' + || in_array(Utils::type($value), $types) + || ($value === [] && in_array('object', $types)) + ) { + return; + } + $msg = 'must be one of the following types: ' . implode(', ', $types) + . '. ' . Utils::type($value) . ' found'; + $this->typeError($from, $msg); + } + + /** + * Validates value A and B, ensures they both are correctly typed, and of + * the same type. + * + * @param string $from String of function:argument_position + * @param array $types Array of valid value types. + * @param mixed $a Value A + * @param mixed $b Value B + */ + private function validateSeq($from, array $types, $a, $b) + { + $ta = Utils::type($a); + $tb = Utils::type($b); + + if ($ta !== $tb) { + $msg = "encountered a type mismatch in sequence: {$ta}, {$tb}"; + $this->typeError($from, $msg); + } + + $typeMatch = ($types && $types[0] == 'any') || in_array($ta, $types); + if (!$typeMatch) { + $msg = 'encountered a type error in sequence. The argument must be ' + . 'an array of ' . implode('|', $types) . ' types. ' + . "Found {$ta}, {$tb}."; + $this->typeError($from, $msg); + } + } + + /** + * Reduces and validates an array of values to a single value using a fn. + * + * @param string $from String of function:argument_position + * @param array $values Values to reduce. + * @param array $types Array of valid value types. + * @param callable $reduce Reduce function that accepts ($carry, $item). + * + * @return mixed + */ + private function reduce($from, array $values, array $types, callable $reduce) + { + $i = -1; + return array_reduce( + $values, + function ($carry, $item) use ($from, $types, $reduce, &$i) { + if (++$i > 0) { + $this->validateSeq($from, $types, $carry, $item); + } + return $reduce($carry, $item, $i); + } + ); + } + + /** + * Validates the return values of expressions as they are applied. + * + * @param string $from Function name : position + * @param callable $expr Expression function to validate. + * @param array $types Array of acceptable return type values. + * + * @return callable Returns a wrapped function + */ + private function wrapExpression($from, callable $expr, array $types) + { + list($fn, $pos) = explode(':', $from); + $from = "The expression return value of argument {$pos} of {$fn}"; + return function ($value) use ($from, $expr, $types) { + $value = $expr($value); + $this->validateType($from, $value, $types); + return $value; + }; + } + + /** @internal Pass function name validation off to runtime */ + public function __call($name, $args) + { + $name = str_replace('fn_', '', $name); + throw new \RuntimeException("Call to undefined function {$name}"); + } +} diff --git a/vendor/mtdowling/jmespath.php/src/JmesPath.php b/vendor/mtdowling/jmespath.php/src/JmesPath.php new file mode 100644 index 0000000..e2c239a --- /dev/null +++ b/vendor/mtdowling/jmespath.php/src/JmesPath.php @@ -0,0 +1,17 @@ + self::STATE_LT, + '>' => self::STATE_GT, + '=' => self::STATE_EQ, + '!' => self::STATE_NOT, + '[' => self::STATE_LBRACKET, + '|' => self::STATE_PIPE, + '&' => self::STATE_AND, + '`' => self::STATE_JSON_LITERAL, + '"' => self::STATE_QUOTED_STRING, + "'" => self::STATE_STRING_LITERAL, + '-' => self::STATE_NUMBER, + '0' => self::STATE_NUMBER, + '1' => self::STATE_NUMBER, + '2' => self::STATE_NUMBER, + '3' => self::STATE_NUMBER, + '4' => self::STATE_NUMBER, + '5' => self::STATE_NUMBER, + '6' => self::STATE_NUMBER, + '7' => self::STATE_NUMBER, + '8' => self::STATE_NUMBER, + '9' => self::STATE_NUMBER, + ' ' => self::STATE_WHITESPACE, + "\t" => self::STATE_WHITESPACE, + "\n" => self::STATE_WHITESPACE, + "\r" => self::STATE_WHITESPACE, + '.' => self::STATE_SINGLE_CHAR, + '*' => self::STATE_SINGLE_CHAR, + ']' => self::STATE_SINGLE_CHAR, + ',' => self::STATE_SINGLE_CHAR, + ':' => self::STATE_SINGLE_CHAR, + '@' => self::STATE_SINGLE_CHAR, + '(' => self::STATE_SINGLE_CHAR, + ')' => self::STATE_SINGLE_CHAR, + '{' => self::STATE_SINGLE_CHAR, + '}' => self::STATE_SINGLE_CHAR, + '_' => self::STATE_IDENTIFIER, + 'A' => self::STATE_IDENTIFIER, + 'B' => self::STATE_IDENTIFIER, + 'C' => self::STATE_IDENTIFIER, + 'D' => self::STATE_IDENTIFIER, + 'E' => self::STATE_IDENTIFIER, + 'F' => self::STATE_IDENTIFIER, + 'G' => self::STATE_IDENTIFIER, + 'H' => self::STATE_IDENTIFIER, + 'I' => self::STATE_IDENTIFIER, + 'J' => self::STATE_IDENTIFIER, + 'K' => self::STATE_IDENTIFIER, + 'L' => self::STATE_IDENTIFIER, + 'M' => self::STATE_IDENTIFIER, + 'N' => self::STATE_IDENTIFIER, + 'O' => self::STATE_IDENTIFIER, + 'P' => self::STATE_IDENTIFIER, + 'Q' => self::STATE_IDENTIFIER, + 'R' => self::STATE_IDENTIFIER, + 'S' => self::STATE_IDENTIFIER, + 'T' => self::STATE_IDENTIFIER, + 'U' => self::STATE_IDENTIFIER, + 'V' => self::STATE_IDENTIFIER, + 'W' => self::STATE_IDENTIFIER, + 'X' => self::STATE_IDENTIFIER, + 'Y' => self::STATE_IDENTIFIER, + 'Z' => self::STATE_IDENTIFIER, + 'a' => self::STATE_IDENTIFIER, + 'b' => self::STATE_IDENTIFIER, + 'c' => self::STATE_IDENTIFIER, + 'd' => self::STATE_IDENTIFIER, + 'e' => self::STATE_IDENTIFIER, + 'f' => self::STATE_IDENTIFIER, + 'g' => self::STATE_IDENTIFIER, + 'h' => self::STATE_IDENTIFIER, + 'i' => self::STATE_IDENTIFIER, + 'j' => self::STATE_IDENTIFIER, + 'k' => self::STATE_IDENTIFIER, + 'l' => self::STATE_IDENTIFIER, + 'm' => self::STATE_IDENTIFIER, + 'n' => self::STATE_IDENTIFIER, + 'o' => self::STATE_IDENTIFIER, + 'p' => self::STATE_IDENTIFIER, + 'q' => self::STATE_IDENTIFIER, + 'r' => self::STATE_IDENTIFIER, + 's' => self::STATE_IDENTIFIER, + 't' => self::STATE_IDENTIFIER, + 'u' => self::STATE_IDENTIFIER, + 'v' => self::STATE_IDENTIFIER, + 'w' => self::STATE_IDENTIFIER, + 'x' => self::STATE_IDENTIFIER, + 'y' => self::STATE_IDENTIFIER, + 'z' => self::STATE_IDENTIFIER, + ]; + + /** @var array Valid identifier characters after first character */ + private $validIdentifier = [ + 'A' => true, 'B' => true, 'C' => true, 'D' => true, 'E' => true, + 'F' => true, 'G' => true, 'H' => true, 'I' => true, 'J' => true, + 'K' => true, 'L' => true, 'M' => true, 'N' => true, 'O' => true, + 'P' => true, 'Q' => true, 'R' => true, 'S' => true, 'T' => true, + 'U' => true, 'V' => true, 'W' => true, 'X' => true, 'Y' => true, + 'Z' => true, 'a' => true, 'b' => true, 'c' => true, 'd' => true, + 'e' => true, 'f' => true, 'g' => true, 'h' => true, 'i' => true, + 'j' => true, 'k' => true, 'l' => true, 'm' => true, 'n' => true, + 'o' => true, 'p' => true, 'q' => true, 'r' => true, 's' => true, + 't' => true, 'u' => true, 'v' => true, 'w' => true, 'x' => true, + 'y' => true, 'z' => true, '_' => true, '0' => true, '1' => true, + '2' => true, '3' => true, '4' => true, '5' => true, '6' => true, + '7' => true, '8' => true, '9' => true, + ]; + + /** @var array Valid number characters after the first character */ + private $numbers = [ + '0' => true, '1' => true, '2' => true, '3' => true, '4' => true, + '5' => true, '6' => true, '7' => true, '8' => true, '9' => true + ]; + + /** @var array Map of simple single character tokens */ + private $simpleTokens = [ + '.' => self::T_DOT, + '*' => self::T_STAR, + ']' => self::T_RBRACKET, + ',' => self::T_COMMA, + ':' => self::T_COLON, + '@' => self::T_CURRENT, + '(' => self::T_LPAREN, + ')' => self::T_RPAREN, + '{' => self::T_LBRACE, + '}' => self::T_RBRACE, + ]; + + /** + * Tokenize the JMESPath expression into an array of tokens hashes that + * contain a 'type', 'value', and 'key'. + * + * @param string $input JMESPath input + * + * @return array + * @throws SyntaxErrorException + */ + public function tokenize($input) + { + $tokens = []; + + if ($input === '') { + goto eof; + } + + $chars = str_split($input); + + while (false !== ($current = current($chars))) { + + // Every character must be in the transition character table. + if (!isset(self::$transitionTable[$current])) { + $tokens[] = [ + 'type' => self::T_UNKNOWN, + 'pos' => key($chars), + 'value' => $current + ]; + next($chars); + continue; + } + + $state = self::$transitionTable[$current]; + + if ($state === self::STATE_SINGLE_CHAR) { + + // Consume simple tokens like ".", ",", "@", etc. + $tokens[] = [ + 'type' => $this->simpleTokens[$current], + 'pos' => key($chars), + 'value' => $current + ]; + next($chars); + + } elseif ($state === self::STATE_IDENTIFIER) { + + // Consume identifiers + $start = key($chars); + $buffer = ''; + do { + $buffer .= $current; + $current = next($chars); + } while ($current !== false && isset($this->validIdentifier[$current])); + $tokens[] = [ + 'type' => self::T_IDENTIFIER, + 'value' => $buffer, + 'pos' => $start + ]; + + } elseif ($state === self::STATE_WHITESPACE) { + + // Skip whitespace + next($chars); + + } elseif ($state === self::STATE_LBRACKET) { + + // Consume "[", "[?", and "[]" + $position = key($chars); + $actual = next($chars); + if ($actual === ']') { + next($chars); + $tokens[] = [ + 'type' => self::T_FLATTEN, + 'pos' => $position, + 'value' => '[]' + ]; + } elseif ($actual === '?') { + next($chars); + $tokens[] = [ + 'type' => self::T_FILTER, + 'pos' => $position, + 'value' => '[?' + ]; + } else { + $tokens[] = [ + 'type' => self::T_LBRACKET, + 'pos' => $position, + 'value' => '[' + ]; + } + + } elseif ($state === self::STATE_STRING_LITERAL) { + + // Consume raw string literals + $t = $this->inside($chars, "'", self::T_LITERAL); + $t['value'] = str_replace("\\'", "'", $t['value']); + $tokens[] = $t; + + } elseif ($state === self::STATE_PIPE) { + + // Consume pipe and OR + $tokens[] = $this->matchOr($chars, '|', '|', self::T_OR, self::T_PIPE); + + } elseif ($state == self::STATE_JSON_LITERAL) { + + // Consume JSON literals + $token = $this->inside($chars, '`', self::T_LITERAL); + if ($token['type'] === self::T_LITERAL) { + $token['value'] = str_replace('\\`', '`', $token['value']); + $token = $this->parseJson($token); + } + $tokens[] = $token; + + } elseif ($state == self::STATE_NUMBER) { + + // Consume numbers + $start = key($chars); + $buffer = ''; + do { + $buffer .= $current; + $current = next($chars); + } while ($current !== false && isset($this->numbers[$current])); + $tokens[] = [ + 'type' => self::T_NUMBER, + 'value' => (int)$buffer, + 'pos' => $start + ]; + + } elseif ($state === self::STATE_QUOTED_STRING) { + + // Consume quoted identifiers + $token = $this->inside($chars, '"', self::T_QUOTED_IDENTIFIER); + if ($token['type'] === self::T_QUOTED_IDENTIFIER) { + $token['value'] = '"' . $token['value'] . '"'; + $token = $this->parseJson($token); + } + $tokens[] = $token; + + } elseif ($state === self::STATE_EQ) { + + // Consume equals + $tokens[] = $this->matchOr($chars, '=', '=', self::T_COMPARATOR, self::T_UNKNOWN); + + } elseif ($state == self::STATE_AND) { + + $tokens[] = $this->matchOr($chars, '&', '&', self::T_AND, self::T_EXPREF); + + } elseif ($state === self::STATE_NOT) { + + // Consume not equal + $tokens[] = $this->matchOr($chars, '!', '=', self::T_COMPARATOR, self::T_NOT); + + } else { + + // either '<' or '>' + // Consume less than and greater than + $tokens[] = $this->matchOr($chars, $current, '=', self::T_COMPARATOR, self::T_COMPARATOR); + + } + } + + eof: + $tokens[] = [ + 'type' => self::T_EOF, + 'pos' => mb_strlen($input, 'UTF-8'), + 'value' => null + ]; + + return $tokens; + } + + /** + * Returns a token based on whether or not the next token matches the + * expected value. If it does, a token of "$type" is returned. Otherwise, + * a token of "$orElse" type is returned. + * + * @param array $chars Array of characters by reference. + * @param string $current The current character. + * @param string $expected Expected character. + * @param string $type Expected result type. + * @param string $orElse Otherwise return a token of this type. + * + * @return array Returns a conditional token. + */ + private function matchOr(array &$chars, $current, $expected, $type, $orElse) + { + if (next($chars) === $expected) { + next($chars); + return [ + 'type' => $type, + 'pos' => key($chars) - 1, + 'value' => $current . $expected + ]; + } + + return [ + 'type' => $orElse, + 'pos' => key($chars) - 1, + 'value' => $current + ]; + } + + /** + * Returns a token the is the result of consuming inside of delimiter + * characters. Escaped delimiters will be adjusted before returning a + * value. If the token is not closed, "unknown" is returned. + * + * @param array $chars Array of characters by reference. + * @param string $delim The delimiter character. + * @param string $type Token type. + * + * @return array Returns the consumed token. + */ + private function inside(array &$chars, $delim, $type) + { + $position = key($chars); + $current = next($chars); + $buffer = ''; + + while ($current !== $delim) { + if ($current === '\\') { + $buffer .= '\\'; + $current = next($chars); + } + if ($current === false) { + // Unclosed delimiter + return [ + 'type' => self::T_UNKNOWN, + 'value' => $buffer, + 'pos' => $position + ]; + } + $buffer .= $current; + $current = next($chars); + } + + next($chars); + + return ['type' => $type, 'value' => $buffer, 'pos' => $position]; + } + + /** + * Parses a JSON token or sets the token type to "unknown" on error. + * + * @param array $token Token that needs parsing. + * + * @return array Returns a token with a parsed value. + */ + private function parseJson(array $token) + { + $value = json_decode($token['value'], true); + + if ($error = json_last_error()) { + // Legacy support for elided quotes. Try to parse again by adding + // quotes around the bad input value. + $value = json_decode('"' . $token['value'] . '"', true); + if ($error = json_last_error()) { + $token['type'] = self::T_UNKNOWN; + return $token; + } + } + + $token['value'] = $value; + return $token; + } +} diff --git a/vendor/mtdowling/jmespath.php/src/Parser.php b/vendor/mtdowling/jmespath.php/src/Parser.php new file mode 100644 index 0000000..1b875da --- /dev/null +++ b/vendor/mtdowling/jmespath.php/src/Parser.php @@ -0,0 +1,519 @@ + T::T_EOF]; + private static $currentNode = ['type' => T::T_CURRENT]; + + private static $bp = [ + T::T_EOF => 0, + T::T_QUOTED_IDENTIFIER => 0, + T::T_IDENTIFIER => 0, + T::T_RBRACKET => 0, + T::T_RPAREN => 0, + T::T_COMMA => 0, + T::T_RBRACE => 0, + T::T_NUMBER => 0, + T::T_CURRENT => 0, + T::T_EXPREF => 0, + T::T_COLON => 0, + T::T_PIPE => 1, + T::T_OR => 2, + T::T_AND => 3, + T::T_COMPARATOR => 5, + T::T_FLATTEN => 9, + T::T_STAR => 20, + T::T_FILTER => 21, + T::T_DOT => 40, + T::T_NOT => 45, + T::T_LBRACE => 50, + T::T_LBRACKET => 55, + T::T_LPAREN => 60, + ]; + + /** @var array Acceptable tokens after a dot token */ + private static $afterDot = [ + T::T_IDENTIFIER => true, // foo.bar + T::T_QUOTED_IDENTIFIER => true, // foo."bar" + T::T_STAR => true, // foo.* + T::T_LBRACE => true, // foo[1] + T::T_LBRACKET => true, // foo{a: 0} + T::T_FILTER => true, // foo.[?bar==10] + ]; + + /** + * @param Lexer $lexer Lexer used to tokenize expressions + */ + public function __construct(Lexer $lexer = null) + { + $this->lexer = $lexer ?: new Lexer(); + } + + /** + * Parses a JMESPath expression into an AST + * + * @param string $expression JMESPath expression to compile + * + * @return array Returns an array based AST + * @throws SyntaxErrorException + */ + public function parse($expression) + { + $this->expression = $expression; + $this->tokens = $this->lexer->tokenize($expression); + $this->tpos = -1; + $this->next(); + $result = $this->expr(); + + if ($this->token['type'] === T::T_EOF) { + return $result; + } + + throw $this->syntax('Did not reach the end of the token stream'); + } + + /** + * Parses an expression while rbp < lbp. + * + * @param int $rbp Right bound precedence + * + * @return array + */ + private function expr($rbp = 0) + { + $left = $this->{"nud_{$this->token['type']}"}(); + while ($rbp < self::$bp[$this->token['type']]) { + $left = $this->{"led_{$this->token['type']}"}($left); + } + + return $left; + } + + private function nud_identifier() + { + $token = $this->token; + $this->next(); + return ['type' => 'field', 'value' => $token['value']]; + } + + private function nud_quoted_identifier() + { + $token = $this->token; + $this->next(); + $this->assertNotToken(T::T_LPAREN); + return ['type' => 'field', 'value' => $token['value']]; + } + + private function nud_current() + { + $this->next(); + return self::$currentNode; + } + + private function nud_literal() + { + $token = $this->token; + $this->next(); + return ['type' => 'literal', 'value' => $token['value']]; + } + + private function nud_expref() + { + $this->next(); + return ['type' => T::T_EXPREF, 'children' => [$this->expr(self::$bp[T::T_EXPREF])]]; + } + + private function nud_not() + { + $this->next(); + return ['type' => T::T_NOT, 'children' => [$this->expr(self::$bp[T::T_NOT])]]; + } + + private function nud_lparen() + { + $this->next(); + $result = $this->expr(0); + if ($this->token['type'] !== T::T_RPAREN) { + throw $this->syntax('Unclosed `(`'); + } + $this->next(); + return $result; + } + + private function nud_lbrace() + { + static $validKeys = [T::T_QUOTED_IDENTIFIER => true, T::T_IDENTIFIER => true]; + $this->next($validKeys); + $pairs = []; + + do { + $pairs[] = $this->parseKeyValuePair(); + if ($this->token['type'] == T::T_COMMA) { + $this->next($validKeys); + } + } while ($this->token['type'] !== T::T_RBRACE); + + $this->next(); + + return['type' => 'multi_select_hash', 'children' => $pairs]; + } + + private function nud_flatten() + { + return $this->led_flatten(self::$currentNode); + } + + private function nud_filter() + { + return $this->led_filter(self::$currentNode); + } + + private function nud_star() + { + return $this->parseWildcardObject(self::$currentNode); + } + + private function nud_lbracket() + { + $this->next(); + $type = $this->token['type']; + if ($type == T::T_NUMBER || $type == T::T_COLON) { + return $this->parseArrayIndexExpression(); + } elseif ($type == T::T_STAR && $this->lookahead() == T::T_RBRACKET) { + return $this->parseWildcardArray(); + } else { + return $this->parseMultiSelectList(); + } + } + + private function led_lbracket(array $left) + { + static $nextTypes = [T::T_NUMBER => true, T::T_COLON => true, T::T_STAR => true]; + $this->next($nextTypes); + switch ($this->token['type']) { + case T::T_NUMBER: + case T::T_COLON: + return [ + 'type' => 'subexpression', + 'children' => [$left, $this->parseArrayIndexExpression()] + ]; + default: + return $this->parseWildcardArray($left); + } + } + + private function led_flatten(array $left) + { + $this->next(); + + return [ + 'type' => 'projection', + 'from' => 'array', + 'children' => [ + ['type' => T::T_FLATTEN, 'children' => [$left]], + $this->parseProjection(self::$bp[T::T_FLATTEN]) + ] + ]; + } + + private function led_dot(array $left) + { + $this->next(self::$afterDot); + + if ($this->token['type'] == T::T_STAR) { + return $this->parseWildcardObject($left); + } + + return [ + 'type' => 'subexpression', + 'children' => [$left, $this->parseDot(self::$bp[T::T_DOT])] + ]; + } + + private function led_or(array $left) + { + $this->next(); + return [ + 'type' => T::T_OR, + 'children' => [$left, $this->expr(self::$bp[T::T_OR])] + ]; + } + + private function led_and(array $left) + { + $this->next(); + return [ + 'type' => T::T_AND, + 'children' => [$left, $this->expr(self::$bp[T::T_AND])] + ]; + } + + private function led_pipe(array $left) + { + $this->next(); + return [ + 'type' => T::T_PIPE, + 'children' => [$left, $this->expr(self::$bp[T::T_PIPE])] + ]; + } + + private function led_lparen(array $left) + { + $args = []; + $this->next(); + + while ($this->token['type'] != T::T_RPAREN) { + $args[] = $this->expr(0); + if ($this->token['type'] == T::T_COMMA) { + $this->next(); + } + } + + $this->next(); + + return [ + 'type' => 'function', + 'value' => $left['value'], + 'children' => $args + ]; + } + + private function led_filter(array $left) + { + $this->next(); + $expression = $this->expr(); + if ($this->token['type'] != T::T_RBRACKET) { + throw $this->syntax('Expected a closing rbracket for the filter'); + } + + $this->next(); + $rhs = $this->parseProjection(self::$bp[T::T_FILTER]); + + return [ + 'type' => 'projection', + 'from' => 'array', + 'children' => [ + $left ?: self::$currentNode, + [ + 'type' => 'condition', + 'children' => [$expression, $rhs] + ] + ] + ]; + } + + private function led_comparator(array $left) + { + $token = $this->token; + $this->next(); + + return [ + 'type' => T::T_COMPARATOR, + 'value' => $token['value'], + 'children' => [$left, $this->expr(self::$bp[T::T_COMPARATOR])] + ]; + } + + private function parseProjection($bp) + { + $type = $this->token['type']; + if (self::$bp[$type] < 10) { + return self::$currentNode; + } elseif ($type == T::T_DOT) { + $this->next(self::$afterDot); + return $this->parseDot($bp); + } elseif ($type == T::T_LBRACKET || $type == T::T_FILTER) { + return $this->expr($bp); + } + + throw $this->syntax('Syntax error after projection'); + } + + private function parseDot($bp) + { + if ($this->token['type'] == T::T_LBRACKET) { + $this->next(); + return $this->parseMultiSelectList(); + } + + return $this->expr($bp); + } + + private function parseKeyValuePair() + { + static $validColon = [T::T_COLON => true]; + $key = $this->token['value']; + $this->next($validColon); + $this->next(); + + return [ + 'type' => 'key_val_pair', + 'value' => $key, + 'children' => [$this->expr()] + ]; + } + + private function parseWildcardObject(array $left = null) + { + $this->next(); + + return [ + 'type' => 'projection', + 'from' => 'object', + 'children' => [ + $left ?: self::$currentNode, + $this->parseProjection(self::$bp[T::T_STAR]) + ] + ]; + } + + private function parseWildcardArray(array $left = null) + { + static $getRbracket = [T::T_RBRACKET => true]; + $this->next($getRbracket); + $this->next(); + + return [ + 'type' => 'projection', + 'from' => 'array', + 'children' => [ + $left ?: self::$currentNode, + $this->parseProjection(self::$bp[T::T_STAR]) + ] + ]; + } + + /** + * Parses an array index expression (e.g., [0], [1:2:3] + */ + private function parseArrayIndexExpression() + { + static $matchNext = [ + T::T_NUMBER => true, + T::T_COLON => true, + T::T_RBRACKET => true + ]; + + $pos = 0; + $parts = [null, null, null]; + $expected = $matchNext; + + do { + if ($this->token['type'] == T::T_COLON) { + $pos++; + $expected = $matchNext; + } elseif ($this->token['type'] == T::T_NUMBER) { + $parts[$pos] = $this->token['value']; + $expected = [T::T_COLON => true, T::T_RBRACKET => true]; + } + $this->next($expected); + } while ($this->token['type'] != T::T_RBRACKET); + + // Consume the closing bracket + $this->next(); + + if ($pos === 0) { + // No colons were found so this is a simple index extraction + return ['type' => 'index', 'value' => $parts[0]]; + } + + if ($pos > 2) { + throw $this->syntax('Invalid array slice syntax: too many colons'); + } + + // Sliced array from start (e.g., [2:]) + return [ + 'type' => 'projection', + 'from' => 'array', + 'children' => [ + ['type' => 'slice', 'value' => $parts], + $this->parseProjection(self::$bp[T::T_STAR]) + ] + ]; + } + + private function parseMultiSelectList() + { + $nodes = []; + + do { + $nodes[] = $this->expr(); + if ($this->token['type'] == T::T_COMMA) { + $this->next(); + $this->assertNotToken(T::T_RBRACKET); + } + } while ($this->token['type'] !== T::T_RBRACKET); + $this->next(); + + return ['type' => 'multi_select_list', 'children' => $nodes]; + } + + private function syntax($msg) + { + return new SyntaxErrorException($msg, $this->token, $this->expression); + } + + private function lookahead() + { + return (!isset($this->tokens[$this->tpos + 1])) + ? T::T_EOF + : $this->tokens[$this->tpos + 1]['type']; + } + + private function next(array $match = null) + { + if (!isset($this->tokens[$this->tpos + 1])) { + $this->token = self::$nullToken; + } else { + $this->token = $this->tokens[++$this->tpos]; + } + + if ($match && !isset($match[$this->token['type']])) { + throw $this->syntax($match); + } + } + + private function assertNotToken($type) + { + if ($this->token['type'] == $type) { + throw $this->syntax("Token {$this->tpos} not allowed to be $type"); + } + } + + /** + * @internal Handles undefined tokens without paying the cost of validation + */ + public function __call($method, $args) + { + $prefix = substr($method, 0, 4); + if ($prefix == 'nud_' || $prefix == 'led_') { + $token = substr($method, 4); + $message = "Unexpected \"$token\" token ($method). Expected one of" + . " the following tokens: " + . implode(', ', array_map(function ($i) { + return '"' . substr($i, 4) . '"'; + }, array_filter( + get_class_methods($this), + function ($i) use ($prefix) { + return strpos($i, $prefix) === 0; + } + ))); + throw $this->syntax($message); + } + + throw new \BadMethodCallException("Call to undefined method $method"); + } +} diff --git a/vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php b/vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php new file mode 100644 index 0000000..65be17e --- /dev/null +++ b/vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php @@ -0,0 +1,36 @@ +createTokenMessage($token, $expectedTypesOrMessage); + parent::__construct($message); + } + + private function createTokenMessage(array $token, array $valid) + { + return sprintf( + 'Expected one of the following: %s; found %s "%s"', + implode(', ', array_keys($valid)), + $token['type'], + $token['value'] + ); + } +} diff --git a/vendor/mtdowling/jmespath.php/src/TreeCompiler.php b/vendor/mtdowling/jmespath.php/src/TreeCompiler.php new file mode 100644 index 0000000..fe27f41 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/src/TreeCompiler.php @@ -0,0 +1,419 @@ +vars = []; + $this->source = $this->indentation = ''; + $this->write("write('use JmesPath\\TreeInterpreter as Ti;') + ->write('use JmesPath\\FnDispatcher as Fd;') + ->write('use JmesPath\\Utils;') + ->write('') + ->write('function %s(Ti $interpreter, $value) {', $fnName) + ->indent() + ->dispatch($ast) + ->write('') + ->write('return $value;') + ->outdent() + ->write('}'); + + return $this->source; + } + + /** + * @param array $node + * @return mixed + */ + private function dispatch(array $node) + { + return $this->{"visit_{$node['type']}"}($node); + } + + /** + * Creates a monotonically incrementing unique variable name by prefix. + * + * @param string $prefix Variable name prefix + * + * @return string + */ + private function makeVar($prefix) + { + if (!isset($this->vars[$prefix])) { + $this->vars[$prefix] = 0; + return '$' . $prefix; + } + + return '$' . $prefix . ++$this->vars[$prefix]; + } + + /** + * Writes the given line of source code. Pass positional arguments to write + * that match the format of sprintf. + * + * @param string $str String to write + * @return $this + */ + private function write($str) + { + $this->source .= $this->indentation; + if (func_num_args() == 1) { + $this->source .= $str . "\n"; + return $this; + } + $this->source .= vsprintf($str, array_slice(func_get_args(), 1)) . "\n"; + return $this; + } + + /** + * Decreases the indentation level of code being written + * @return $this + */ + private function outdent() + { + $this->indentation = substr($this->indentation, 0, -4); + return $this; + } + + /** + * Increases the indentation level of code being written + * @return $this + */ + private function indent() + { + $this->indentation .= ' '; + return $this; + } + + private function visit_or(array $node) + { + $a = $this->makeVar('beforeOr'); + return $this + ->write('%s = $value;', $a) + ->dispatch($node['children'][0]) + ->write('if (!$value && $value !== "0" && $value !== 0) {') + ->indent() + ->write('$value = %s;', $a) + ->dispatch($node['children'][1]) + ->outdent() + ->write('}'); + } + + private function visit_and(array $node) + { + $a = $this->makeVar('beforeAnd'); + return $this + ->write('%s = $value;', $a) + ->dispatch($node['children'][0]) + ->write('if ($value || $value === "0" || $value === 0) {') + ->indent() + ->write('$value = %s;', $a) + ->dispatch($node['children'][1]) + ->outdent() + ->write('}'); + } + + private function visit_not(array $node) + { + return $this + ->write('// Visiting not node') + ->dispatch($node['children'][0]) + ->write('// Applying boolean not to result of not node') + ->write('$value = !Utils::isTruthy($value);'); + } + + private function visit_subexpression(array $node) + { + return $this + ->dispatch($node['children'][0]) + ->write('if ($value !== null) {') + ->indent() + ->dispatch($node['children'][1]) + ->outdent() + ->write('}'); + } + + private function visit_field(array $node) + { + $arr = '$value[' . var_export($node['value'], true) . ']'; + $obj = '$value->{' . var_export($node['value'], true) . '}'; + $this->write('if (is_array($value) || $value instanceof \\ArrayAccess) {') + ->indent() + ->write('$value = isset(%s) ? %s : null;', $arr, $arr) + ->outdent() + ->write('} elseif ($value instanceof \\stdClass) {') + ->indent() + ->write('$value = isset(%s) ? %s : null;', $obj, $obj) + ->outdent() + ->write("} else {") + ->indent() + ->write('$value = null;') + ->outdent() + ->write("}"); + + return $this; + } + + private function visit_index(array $node) + { + if ($node['value'] >= 0) { + $check = '$value[' . $node['value'] . ']'; + return $this->write( + '$value = (is_array($value) || $value instanceof \\ArrayAccess)' + . ' && isset(%s) ? %s : null;', + $check, $check + ); + } + + $a = $this->makeVar('count'); + return $this + ->write('if (is_array($value) || ($value instanceof \\ArrayAccess && $value instanceof \\Countable)) {') + ->indent() + ->write('%s = count($value) + %s;', $a, $node['value']) + ->write('$value = isset($value[%s]) ? $value[%s] : null;', $a, $a) + ->outdent() + ->write('} else {') + ->indent() + ->write('$value = null;') + ->outdent() + ->write('}'); + } + + private function visit_literal(array $node) + { + return $this->write('$value = %s;', var_export($node['value'], true)); + } + + private function visit_pipe(array $node) + { + return $this + ->dispatch($node['children'][0]) + ->dispatch($node['children'][1]); + } + + private function visit_multi_select_list(array $node) + { + return $this->visit_multi_select_hash($node); + } + + private function visit_multi_select_hash(array $node) + { + $listVal = $this->makeVar('list'); + $value = $this->makeVar('prev'); + $this->write('if ($value !== null) {') + ->indent() + ->write('%s = [];', $listVal) + ->write('%s = $value;', $value); + + $first = true; + foreach ($node['children'] as $child) { + if (!$first) { + $this->write('$value = %s;', $value); + } + $first = false; + if ($node['type'] == 'multi_select_hash') { + $this->dispatch($child['children'][0]); + $key = var_export($child['value'], true); + $this->write('%s[%s] = $value;', $listVal, $key); + } else { + $this->dispatch($child); + $this->write('%s[] = $value;', $listVal); + } + } + + return $this + ->write('$value = %s;', $listVal) + ->outdent() + ->write('}'); + } + + private function visit_function(array $node) + { + $value = $this->makeVar('val'); + $args = $this->makeVar('args'); + $this->write('%s = $value;', $value) + ->write('%s = [];', $args); + + foreach ($node['children'] as $arg) { + $this->dispatch($arg); + $this->write('%s[] = $value;', $args) + ->write('$value = %s;', $value); + } + + return $this->write( + '$value = Fd::getInstance()->__invoke("%s", %s);', + $node['value'], $args + ); + } + + private function visit_slice(array $node) + { + return $this + ->write('$value = !is_string($value) && !Utils::isArray($value)') + ->write(' ? null : Utils::slice($value, %s, %s, %s);', + var_export($node['value'][0], true), + var_export($node['value'][1], true), + var_export($node['value'][2], true) + ); + } + + private function visit_current(array $node) + { + return $this->write('// Visiting current node (no-op)'); + } + + private function visit_expref(array $node) + { + $child = var_export($node['children'][0], true); + return $this->write('$value = function ($value) use ($interpreter) {') + ->indent() + ->write('return $interpreter->visit(%s, $value);', $child) + ->outdent() + ->write('};'); + } + + private function visit_flatten(array $node) + { + $this->dispatch($node['children'][0]); + $merged = $this->makeVar('merged'); + $val = $this->makeVar('val'); + + $this + ->write('// Visiting merge node') + ->write('if (!Utils::isArray($value)) {') + ->indent() + ->write('$value = null;') + ->outdent() + ->write('} else {') + ->indent() + ->write('%s = [];', $merged) + ->write('foreach ($value as %s) {', $val) + ->indent() + ->write('if (is_array(%s) && isset(%s[0])) {', $val, $val) + ->indent() + ->write('%s = array_merge(%s, %s);', $merged, $merged, $val) + ->outdent() + ->write('} elseif (%s !== []) {', $val) + ->indent() + ->write('%s[] = %s;', $merged, $val) + ->outdent() + ->write('}') + ->outdent() + ->write('}') + ->write('$value = %s;', $merged) + ->outdent() + ->write('}'); + + return $this; + } + + private function visit_projection(array $node) + { + $val = $this->makeVar('val'); + $collected = $this->makeVar('collected'); + $this->write('// Visiting projection node') + ->dispatch($node['children'][0]) + ->write(''); + + if (!isset($node['from'])) { + $this->write('if (!is_array($value) || !($value instanceof \stdClass)) { $value = null; }'); + } elseif ($node['from'] == 'object') { + $this->write('if (!Utils::isObject($value)) { $value = null; }'); + } elseif ($node['from'] == 'array') { + $this->write('if (!Utils::isArray($value)) { $value = null; }'); + } + + $this->write('if ($value !== null) {') + ->indent() + ->write('%s = [];', $collected) + ->write('foreach ((array) $value as %s) {', $val) + ->indent() + ->write('$value = %s;', $val) + ->dispatch($node['children'][1]) + ->write('if ($value !== null) {') + ->indent() + ->write('%s[] = $value;', $collected) + ->outdent() + ->write('}') + ->outdent() + ->write('}') + ->write('$value = %s;', $collected) + ->outdent() + ->write('}'); + + return $this; + } + + private function visit_condition(array $node) + { + $value = $this->makeVar('beforeCondition'); + return $this + ->write('%s = $value;', $value) + ->write('// Visiting condition node') + ->dispatch($node['children'][0]) + ->write('// Checking result of condition node') + ->write('if (Utils::isTruthy($value)) {') + ->indent() + ->write('$value = %s;', $value) + ->dispatch($node['children'][1]) + ->outdent() + ->write('} else {') + ->indent() + ->write('$value = null;') + ->outdent() + ->write('}'); + } + + private function visit_comparator(array $node) + { + $value = $this->makeVar('val'); + $a = $this->makeVar('left'); + $b = $this->makeVar('right'); + + $this + ->write('// Visiting comparator node') + ->write('%s = $value;', $value) + ->dispatch($node['children'][0]) + ->write('%s = $value;', $a) + ->write('$value = %s;', $value) + ->dispatch($node['children'][1]) + ->write('%s = $value;', $b); + + if ($node['value'] == '==') { + $this->write('$value = Utils::isEqual(%s, %s);', $a, $b); + } elseif ($node['value'] == '!=') { + $this->write('$value = !Utils::isEqual(%s, %s);', $a, $b); + } else { + $this->write( + '$value = (is_int(%s) || is_float(%s)) && (is_int(%s) || is_float(%s)) && %s %s %s;', + $a, $a, $b, $b, $a, $node['value'], $b + ); + } + + return $this; + } + + /** @internal */ + public function __call($method, $args) + { + throw new \RuntimeException( + sprintf('Invalid node encountered: %s', json_encode($args[0])) + ); + } +} diff --git a/vendor/mtdowling/jmespath.php/src/TreeInterpreter.php b/vendor/mtdowling/jmespath.php/src/TreeInterpreter.php new file mode 100644 index 0000000..cdfdd99 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/src/TreeInterpreter.php @@ -0,0 +1,235 @@ +fnDispatcher = $fnDispatcher ?: FnDispatcher::getInstance(); + } + + /** + * Visits each node in a JMESPath AST and returns the evaluated result. + * + * @param array $node JMESPath AST node + * @param mixed $data Data to evaluate + * + * @return mixed + */ + public function visit(array $node, $data) + { + return $this->dispatch($node, $data); + } + + /** + * Recursively traverses an AST using depth-first, pre-order traversal. + * The evaluation logic for each node type is embedded into a large switch + * statement to avoid the cost of "double dispatch". + * @return mixed + */ + private function dispatch(array $node, $value) + { + $dispatcher = $this->fnDispatcher; + + switch ($node['type']) { + + case 'field': + if (is_array($value) || $value instanceof \ArrayAccess) { + return isset($value[$node['value']]) ? $value[$node['value']] : null; + } elseif ($value instanceof \stdClass) { + return isset($value->{$node['value']}) ? $value->{$node['value']} : null; + } + return null; + + case 'subexpression': + return $this->dispatch( + $node['children'][1], + $this->dispatch($node['children'][0], $value) + ); + + case 'index': + if (!Utils::isArray($value)) { + return null; + } + $idx = $node['value'] >= 0 + ? $node['value'] + : $node['value'] + count($value); + return isset($value[$idx]) ? $value[$idx] : null; + + case 'projection': + $left = $this->dispatch($node['children'][0], $value); + switch ($node['from']) { + case 'object': + if (!Utils::isObject($left)) { + return null; + } + break; + case 'array': + if (!Utils::isArray($left)) { + return null; + } + break; + default: + if (!is_array($left) || !($left instanceof \stdClass)) { + return null; + } + } + + $collected = []; + foreach ((array) $left as $val) { + $result = $this->dispatch($node['children'][1], $val); + if ($result !== null) { + $collected[] = $result; + } + } + + return $collected; + + case 'flatten': + static $skipElement = []; + $value = $this->dispatch($node['children'][0], $value); + + if (!Utils::isArray($value)) { + return null; + } + + $merged = []; + foreach ($value as $values) { + // Only merge up arrays lists and not hashes + if (is_array($values) && isset($values[0])) { + $merged = array_merge($merged, $values); + } elseif ($values !== $skipElement) { + $merged[] = $values; + } + } + + return $merged; + + case 'literal': + return $node['value']; + + case 'current': + return $value; + + case 'or': + $result = $this->dispatch($node['children'][0], $value); + return Utils::isTruthy($result) + ? $result + : $this->dispatch($node['children'][1], $value); + + case 'and': + $result = $this->dispatch($node['children'][0], $value); + return Utils::isTruthy($result) + ? $this->dispatch($node['children'][1], $value) + : $result; + + case 'not': + return !Utils::isTruthy( + $this->dispatch($node['children'][0], $value) + ); + + case 'pipe': + return $this->dispatch( + $node['children'][1], + $this->dispatch($node['children'][0], $value) + ); + + case 'multi_select_list': + if ($value === null) { + return null; + } + + $collected = []; + foreach ($node['children'] as $node) { + $collected[] = $this->dispatch($node, $value); + } + + return $collected; + + case 'multi_select_hash': + if ($value === null) { + return null; + } + + $collected = []; + foreach ($node['children'] as $node) { + $collected[$node['value']] = $this->dispatch( + $node['children'][0], + $value + ); + } + + return $collected; + + case 'comparator': + $left = $this->dispatch($node['children'][0], $value); + $right = $this->dispatch($node['children'][1], $value); + if ($node['value'] == '==') { + return Utils::isEqual($left, $right); + } elseif ($node['value'] == '!=') { + return !Utils::isEqual($left, $right); + } else { + return self::relativeCmp($left, $right, $node['value']); + } + + case 'condition': + return Utils::isTruthy($this->dispatch($node['children'][0], $value)) + ? $this->dispatch($node['children'][1], $value) + : null; + + case 'function': + $args = []; + foreach ($node['children'] as $arg) { + $args[] = $this->dispatch($arg, $value); + } + return $dispatcher($node['value'], $args); + + case 'slice': + return is_string($value) || Utils::isArray($value) + ? Utils::slice( + $value, + $node['value'][0], + $node['value'][1], + $node['value'][2] + ) : null; + + case 'expref': + $apply = $node['children'][0]; + return function ($value) use ($apply) { + return $this->visit($apply, $value); + }; + + default: + throw new \RuntimeException("Unknown node type: {$node['type']}"); + } + } + + /** + * @return bool + */ + private static function relativeCmp($left, $right, $cmp) + { + if (!(is_int($left) || is_float($left)) || !(is_int($right) || is_float($right))) { + return false; + } + + switch ($cmp) { + case '>': return $left > $right; + case '>=': return $left >= $right; + case '<': return $left < $right; + case '<=': return $left <= $right; + default: throw new \RuntimeException("Invalid comparison: $cmp"); + } + } +} diff --git a/vendor/mtdowling/jmespath.php/src/Utils.php b/vendor/mtdowling/jmespath.php/src/Utils.php new file mode 100644 index 0000000..ce4668a --- /dev/null +++ b/vendor/mtdowling/jmespath.php/src/Utils.php @@ -0,0 +1,233 @@ + 'boolean', + 'string' => 'string', + 'NULL' => 'null', + 'double' => 'number', + 'float' => 'number', + 'integer' => 'number' + ]; + + /** + * Returns true if the value is truthy + * + * @param mixed $value Value to check + * + * @return bool + */ + public static function isTruthy($value) + { + if (!$value) { + return $value === 0 || $value === '0'; + } elseif ($value instanceof \stdClass) { + return (bool) get_object_vars($value); + } else { + return true; + } + } + + /** + * Gets the JMESPath type equivalent of a PHP variable. + * + * @param mixed $arg PHP variable + * @return string Returns the JSON data type + * @throws \InvalidArgumentException when an unknown type is given. + */ + public static function type($arg) + { + $type = gettype($arg); + if (isset(self::$typeMap[$type])) { + return self::$typeMap[$type]; + } elseif ($type === 'array') { + if (empty($arg)) { + return 'array'; + } + reset($arg); + return key($arg) === 0 ? 'array' : 'object'; + } elseif ($arg instanceof \stdClass) { + return 'object'; + } elseif ($arg instanceof \Closure) { + return 'expression'; + } elseif ($arg instanceof \ArrayAccess + && $arg instanceof \Countable + ) { + return count($arg) == 0 || $arg->offsetExists(0) + ? 'array' + : 'object'; + } elseif (method_exists($arg, '__toString')) { + return 'string'; + } + + throw new \InvalidArgumentException( + 'Unable to determine JMESPath type from ' . get_class($arg) + ); + } + + /** + * Determine if the provided value is a JMESPath compatible object. + * + * @param mixed $value + * + * @return bool + */ + public static function isObject($value) + { + if (is_array($value)) { + return !$value || array_keys($value)[0] !== 0; + } + + // Handle array-like values. Must be empty or offset 0 does not exist + return $value instanceof \Countable && $value instanceof \ArrayAccess + ? count($value) == 0 || !$value->offsetExists(0) + : $value instanceof \stdClass; + } + + /** + * Determine if the provided value is a JMESPath compatible array. + * + * @param mixed $value + * + * @return bool + */ + public static function isArray($value) + { + if (is_array($value)) { + return !$value || array_keys($value)[0] === 0; + } + + // Handle array-like values. Must be empty or offset 0 exists. + return $value instanceof \Countable && $value instanceof \ArrayAccess + ? count($value) == 0 || $value->offsetExists(0) + : false; + } + + /** + * JSON aware value comparison function. + * + * @param mixed $a First value to compare + * @param mixed $b Second value to compare + * + * @return bool + */ + public static function isEqual($a, $b) + { + if ($a === $b) { + return true; + } elseif ($a instanceof \stdClass) { + return self::isEqual((array) $a, $b); + } elseif ($b instanceof \stdClass) { + return self::isEqual($a, (array) $b); + } else { + return false; + } + } + + /** + * JMESPath requires a stable sorting algorithm, so here we'll implement + * a simple Schwartzian transform that uses array index positions as tie + * breakers. + * + * @param array $data List or map of data to sort + * @param callable $sortFn Callable used to sort values + * + * @return array Returns the sorted array + * @link http://en.wikipedia.org/wiki/Schwartzian_transform + */ + public static function stableSort(array $data, callable $sortFn) + { + // Decorate each item by creating an array of [value, index] + array_walk($data, function (&$v, $k) { + $v = [$v, $k]; + }); + // Sort by the sort function and use the index as a tie-breaker + uasort($data, function ($a, $b) use ($sortFn) { + return $sortFn($a[0], $b[0]) ?: ($a[1] < $b[1] ? -1 : 1); + }); + + // Undecorate each item and return the resulting sorted array + return array_map(function ($v) { + return $v[0]; + }, array_values($data)); + } + + /** + * Creates a Python-style slice of a string or array. + * + * @param array|string $value Value to slice + * @param int|null $start Starting position + * @param int|null $stop Stop position + * @param int $step Step (1, 2, -1, -2, etc.) + * + * @return array|string + * @throws \InvalidArgumentException + */ + public static function slice($value, $start = null, $stop = null, $step = 1) + { + if (!is_array($value) && !is_string($value)) { + throw new \InvalidArgumentException('Expects string or array'); + } + + return self::sliceIndices($value, $start, $stop, $step); + } + + private static function adjustEndpoint($length, $endpoint, $step) + { + if ($endpoint < 0) { + $endpoint += $length; + if ($endpoint < 0) { + $endpoint = $step < 0 ? -1 : 0; + } + } elseif ($endpoint >= $length) { + $endpoint = $step < 0 ? $length - 1 : $length; + } + + return $endpoint; + } + + private static function adjustSlice($length, $start, $stop, $step) + { + if ($step === null) { + $step = 1; + } elseif ($step === 0) { + throw new \RuntimeException('step cannot be 0'); + } + + if ($start === null) { + $start = $step < 0 ? $length - 1 : 0; + } else { + $start = self::adjustEndpoint($length, $start, $step); + } + + if ($stop === null) { + $stop = $step < 0 ? -1 : $length; + } else { + $stop = self::adjustEndpoint($length, $stop, $step); + } + + return [$start, $stop, $step]; + } + + private static function sliceIndices($subject, $start, $stop, $step) + { + $type = gettype($subject); + $len = $type == 'string' ? mb_strlen($subject, 'UTF-8') : count($subject); + list($start, $stop, $step) = self::adjustSlice($len, $start, $stop, $step); + + $result = []; + if ($step > 0) { + for ($i = $start; $i < $stop; $i += $step) { + $result[] = $subject[$i]; + } + } else { + for ($i = $start; $i > $stop; $i += $step) { + $result[] = $subject[$i]; + } + } + + return $type == 'string' ? implode('', $result) : $result; + } +} diff --git a/vendor/mtdowling/jmespath.php/tests/ComplianceTest.php b/vendor/mtdowling/jmespath.php/tests/ComplianceTest.php new file mode 100644 index 0000000..92e86ac --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/ComplianceTest.php @@ -0,0 +1,138 @@ +getMessage(), + $e->getFile(), + $e->getLine() + ); + } + + $file = __DIR__ . '/compliance/' . $file . '.json'; + $failure .= "\n{$compiledStr}php bin/jp.php --file {$file} --suite {$suite} --case {$case}\n\n" + . "Result: " . $this->prettyJson($evalResult) . "\n\n" + . "Expected: " . $this->prettyJson($result) . "\n\n"; + $failure .= 'Associative? ' . var_export($asAssoc, true) . "\n\n"; + + if (!$error && $failed) { + $this->fail("Should not have failed\n{$failure}=> {$failed} {$failureMsg}"); + } elseif ($error && !$failed) { + $this->fail("Should have failed\n{$failure}"); + } + + $this->assertEquals( + $this->convertAssoc($result), + $this->convertAssoc($evalResult), + $failure + ); + } + + public function complianceProvider() + { + $cases = []; + + $files = array_map(function ($f) { + return basename($f, '.json'); + }, glob(__DIR__ . '/compliance/*.json')); + + foreach ($files as $name) { + $contents = file_get_contents(__DIR__ . "/compliance/{$name}.json"); + foreach ([true, false] as $asAssoc) { + $json = json_decode($contents, true); + $jsonObj = json_decode($contents); + foreach ($json as $suiteNumber => $suite) { + $given = $asAssoc ? $suite['given'] : $jsonObj[$suiteNumber]->given; + foreach ($suite['cases'] as $caseNumber => $case) { + $caseData = [ + $given, + $case['expression'], + isset($case['result']) ? $case['result'] : null, + isset($case['error']) ? $case['error'] : false, + $name, + $suiteNumber, + $caseNumber, + false, + $asAssoc + ]; + $cases[] = $caseData; + $caseData[7] = true; + $cases[] = $caseData; + } + } + } + } + + return $cases; + } + + private function convertAssoc($data) + { + if ($data instanceof \stdClass) { + return $this->convertAssoc((array) $data); + } elseif (is_array($data)) { + return array_map([$this, 'convertAssoc'], $data); + } else { + return $data; + } + } + + private function prettyJson($json) + { + if (defined('JSON_PRETTY_PRINT')) { + return json_encode($json, JSON_PRETTY_PRINT); + } + + return json_encode($json); + } +} diff --git a/vendor/mtdowling/jmespath.php/tests/EnvTest.php b/vendor/mtdowling/jmespath.php/tests/EnvTest.php new file mode 100644 index 0000000..5b0d016 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/EnvTest.php @@ -0,0 +1,32 @@ + 123]; + $this->assertEquals(123, Env::search('foo', $data)); + $this->assertEquals(123, Env::search('foo', $data)); + } + + public function testSearchesWithFunction() + { + $data = ['foo' => 123]; + $this->assertEquals(123, \JmesPath\search('foo', $data)); + } + + public function testCleansCompileDir() + { + $dir = sys_get_temp_dir(); + $runtime = new CompilerRuntime($dir); + $runtime('@ | @ | @[0][0][0]', []); + $this->assertNotEmpty(glob($dir . '/jmespath_*.php')); + $this->assertGreaterThan(0, Env::cleanCompileDir()); + $this->assertEmpty(glob($dir . '/jmespath_*.php')); + } +} diff --git a/vendor/mtdowling/jmespath.php/tests/FnDispatcherTest.php b/vendor/mtdowling/jmespath.php/tests/FnDispatcherTest.php new file mode 100644 index 0000000..3ee48e7 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/FnDispatcherTest.php @@ -0,0 +1,42 @@ +assertEquals('foo', $fn('to_string', ['foo'])); + $this->assertEquals('1', $fn('to_string', [1])); + $this->assertEquals('["foo"]', $fn('to_string', [['foo']])); + $std = new \stdClass(); + $std->foo = 'bar'; + $this->assertEquals('{"foo":"bar"}', $fn('to_string', [$std])); + $this->assertEquals('foo', $fn('to_string', [new _TestStringClass()])); + $this->assertEquals('"foo"', $fn('to_string', [new _TestJsonStringClass()])); + } +} + +class _TestStringClass +{ + public function __toString() + { + return 'foo'; + } +} + +class _TestJsonStringClass implements \JsonSerializable +{ + public function __toString() + { + return 'no!'; + } + + public function jsonSerialize() + { + return 'foo'; + } +} diff --git a/vendor/mtdowling/jmespath.php/tests/LexerTest.php b/vendor/mtdowling/jmespath.php/tests/LexerTest.php new file mode 100644 index 0000000..43094a9 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/LexerTest.php @@ -0,0 +1,89 @@ +tokenize($input); + $this->assertEquals($tokens[0]['type'], $type); + } + + public function testTokenizesJsonLiterals() + { + $l = new Lexer(); + $tokens = $l->tokenize('`null`, `false`, `true`, `"abc"`, `"ab\\"c"`,' + . '`0`, `0.45`, `-0.5`'); + $this->assertNull($tokens[0]['value']); + $this->assertFalse($tokens[2]['value']); + $this->assertTrue($tokens[4]['value']); + $this->assertEquals('abc', $tokens[6]['value']); + $this->assertEquals('ab"c', $tokens[8]['value']); + $this->assertSame(0, $tokens[10]['value']); + $this->assertSame(0.45, $tokens[12]['value']); + $this->assertSame(-0.5, $tokens[14]['value']); + } + + public function testTokenizesJsonNumbers() + { + $l = new Lexer(); + $tokens = $l->tokenize('`10`, `1.2`, `-10.20e-10`, `1.2E+2`'); + $this->assertEquals(10, $tokens[0]['value']); + $this->assertEquals(1.2, $tokens[2]['value']); + $this->assertEquals(-1.02E-9, $tokens[4]['value']); + $this->assertEquals(120, $tokens[6]['value']); + } + + public function testCanWorkWithElidedJsonLiterals() + { + $l = new Lexer(); + $tokens = $l->tokenize('`foo`'); + $this->assertEquals('foo', $tokens[0]['value']); + $this->assertEquals('literal', $tokens[0]['type']); + } +} diff --git a/vendor/mtdowling/jmespath.php/tests/ParserTest.php b/vendor/mtdowling/jmespath.php/tests/ParserTest.php new file mode 100644 index 0000000..719f179 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/ParserTest.php @@ -0,0 +1,51 @@ +parse('.bar'); + } + + /** + * @expectedException \JmesPath\SyntaxErrorException + * @expectedExceptionMessage Syntax error at character 1 + */ + public function testThrowsSyntaxErrorForInvalidSequence() + { + $p = new Parser(new Lexer()); + $p->parse('a,'); + } + + /** + * @expectedException \JmesPath\SyntaxErrorException + * @expectedExceptionMessage Syntax error at character 2 + */ + public function testMatchesAfterFirstToken() + { + $p = new Parser(new Lexer()); + $p->parse('a.,'); + } + + /** + * @expectedException \JmesPath\SyntaxErrorException + * @expectedExceptionMessage Unexpected "eof" token + */ + public function testHandlesEmptyExpressions() + { + (new Parser(new Lexer()))->parse(''); + } +} diff --git a/vendor/mtdowling/jmespath.php/tests/SyntaxErrorExceptionTest.php b/vendor/mtdowling/jmespath.php/tests/SyntaxErrorExceptionTest.php new file mode 100644 index 0000000..139fdfb --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/SyntaxErrorExceptionTest.php @@ -0,0 +1,43 @@ + 'comma', 'pos' => 3, 'value' => ','], + 'abc,def' + ); + $expected = <<assertContains($expected, $e->getMessage()); + } + + public function testCreatesWithArray() + { + $e = new SyntaxErrorException( + ['dot' => true, 'eof' => true], + ['type' => 'comma', 'pos' => 3, 'value' => ','], + 'abc,def' + ); + $expected = <<assertContains($expected, $e->getMessage()); + } +} diff --git a/vendor/mtdowling/jmespath.php/tests/TreeCompilerTest.php b/vendor/mtdowling/jmespath.php/tests/TreeCompilerTest.php new file mode 100644 index 0000000..99fdbb9 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/TreeCompilerTest.php @@ -0,0 +1,24 @@ +visit( + ['type' => 'field', 'value' => 'foo'], + 'testing', + 'foo' + ); + $this->assertContains('assertContains('$value = isset($value->{\'foo\'}) ? $value->{\'foo\'} : null;', $source); + $this->assertContains('$value = isset($value[\'foo\']) ? $value[\'foo\'] : null;', $source); + } +} diff --git a/vendor/mtdowling/jmespath.php/tests/TreeInterpreterTest.php b/vendor/mtdowling/jmespath.php/tests/TreeInterpreterTest.php new file mode 100644 index 0000000..f9eb92d --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/TreeInterpreterTest.php @@ -0,0 +1,72 @@ +assertNull($t->visit([ + 'type' => 'flatten', + 'children' => [ + ['type' => 'literal', 'value' => 1], + ['type' => 'literal', 'value' => 1] + ] + ], [], [ + 'runtime' => new AstRuntime() + ])); + } + + public function testWorksWithArrayObjectAsObject() + { + $runtime = new AstRuntime(); + $this->assertEquals('baz', $runtime('foo.bar', new \ArrayObject([ + 'foo' => new \ArrayObject(['bar' => 'baz']) + ]))); + } + + public function testWorksWithArrayObjectAsArray() + { + $runtime = new AstRuntime(); + $this->assertEquals('baz', $runtime('foo[0].bar', new \ArrayObject([ + 'foo' => new \ArrayObject([new \ArrayObject(['bar' => 'baz'])]) + ]))); + } + + public function testWorksWithArrayProjections() + { + $runtime = new AstRuntime(); + $this->assertEquals( + ['baz'], + $runtime('foo[*].bar', new \ArrayObject([ + 'foo' => new \ArrayObject([ + new \ArrayObject([ + 'bar' => 'baz' + ]) + ]) + ])) + ); + } + + public function testWorksWithObjectProjections() + { + $runtime = new AstRuntime(); + $this->assertEquals( + ['baz'], + $runtime('foo.*.bar', new \ArrayObject([ + 'foo' => new \ArrayObject([ + 'abc' => new \ArrayObject([ + 'bar' => 'baz' + ]) + ]) + ])) + ); + } +} diff --git a/vendor/mtdowling/jmespath.php/tests/UtilsTest.php b/vendor/mtdowling/jmespath.php/tests/UtilsTest.php new file mode 100644 index 0000000..b09978d --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/UtilsTest.php @@ -0,0 +1,131 @@ + 1], 'object'], + [new \stdClass(), 'object'], + [function () {}, 'expression'], + [new \ArrayObject(), 'array'], + [new \ArrayObject([1, 2]), 'array'], + [new \ArrayObject(['foo' => 'bar']), 'object'], + [new _TestStr(), 'string'] + ]; + } + + /** + * @dataProvider typeProvider + */ + public function testGetsTypes($given, $type) + { + $this->assertEquals($type, Utils::type($given)); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testThrowsForInvalidArg() + { + Utils::type(new _TestClass()); + } + + public function isArrayProvider() + { + return [ + [[], true], + [[1, 2], true], + [['a' => 1], false], + [new _TestClass(), false], + [new \ArrayObject(['a' => 'b']), false], + [new \ArrayObject([1]), true], + [new \stdClass(), false] + ]; + } + + /** + * @dataProvider isArrayProvider + */ + public function testChecksIfArray($given, $result) + { + $this->assertSame($result, Utils::isArray($given)); + } + + public function isObjectProvider() + { + return [ + [[], true], + [[1, 2], false], + [['a' => 1], true], + [new _TestClass(), false], + [new \ArrayObject(['a' => 'b']), true], + [new \ArrayObject([1]), false], + [new \stdClass(), true] + ]; + } + + /** + * @dataProvider isObjectProvider + */ + public function testChecksIfObject($given, $result) + { + $this->assertSame($result, Utils::isObject($given)); + } + + public function testHasStableSort() + { + $data = [new _TestStr(), new _TestStr(), 0, 10, 2]; + $result = Utils::stableSort($data, function ($a, $b) { + $a = (int) (string) $a; + $b = (int) (string) $b; + return $a > $b ? -1 : ($a == $b ? 0 : 1); + }); + $this->assertSame($data[0], $result[0]); + $this->assertSame($data[1], $result[1]); + $this->assertEquals(10, $result[2]); + $this->assertEquals(2, $result[3]); + $this->assertEquals(0, $result[4]); + } + + public function testSlicesArrays() + { + $this->assertEquals([3, 2, 1], Utils::slice([1, 2, 3], null, null, -1)); + $this->assertEquals([1, 3], Utils::slice([1, 2, 3], null, null, 2)); + $this->assertEquals([2, 3], Utils::slice([1, 2, 3], 1)); + } + + public function testSlicesStrings() + { + $this->assertEquals('cba', Utils::slice('abc', null, null, -1)); + $this->assertEquals('ac', Utils::slice('abc', null, null, 2)); + $this->assertEquals('bc', Utils::slice('abc', 1)); + } +} + +class _TestClass implements \ArrayAccess +{ + public function offsetExists($offset) {} + public function offsetGet($offset) {} + public function offsetSet($offset, $value) {} + public function offsetUnset($offset) {} +} + +class _TestStr +{ + public function __toString() + { + return '100'; + } +} diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/basic.json b/vendor/mtdowling/jmespath.php/tests/compliance/basic.json new file mode 100644 index 0000000..f06e9ae --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/basic.json @@ -0,0 +1,112 @@ +[{ + "given": + {"foo": {"bar": {"baz": "correct"}}}, + "cases": [ + { + "expression": "foo", + "result": {"bar": {"baz": "correct"}} + }, + { + "expression": "foo.bar", + "result": {"baz": "correct"} + }, + { + "expression": "foo.bar.baz", + "result": "correct" + }, + { + "expression": "foo\n.\nbar\n.baz", + "result": "correct" + }, + { + "expression": "foo . bar . baz", + "result": "correct" + }, + { + "expression": "foo\t.\tbar\t.\tbaz", + "result": "correct" + }, + { + "expression": "foo\r.\rbar\r.\rbaz", + "result": "correct" + }, + { + "expression": "foo\r\n.\r\nbar\r\n.\r\nbaz", + "result": "correct" + }, + { + "expression": "foo.bar.baz.bad", + "result": null + }, + { + "expression": "foo.bar.bad", + "result": null + }, + { + "expression": "foo.bad", + "result": null + }, + { + "expression": "bad", + "result": null + }, + { + "expression": "bad.morebad.morebad", + "result": null + } + ] +}, +{ + "given": + {"foo": {"bar": ["one", "two", "three"]}}, + "cases": [ + { + "expression": "foo", + "result": {"bar": ["one", "two", "three"]} + }, + { + "expression": "foo.bar", + "result": ["one", "two", "three"] + } + ] +}, +{ + "given": ["one", "two", "three"], + "cases": [ + { + "expression": "one", + "result": null + }, + { + "expression": "two", + "result": null + }, + { + "expression": "three", + "result": null + }, + { + "expression": "one.two", + "result": null + } + ] +}, +{ + "given": + {"foo": {"1": ["one", "two", "three"], "-1": "bar"}}, + "cases": [ + { + "expression": "foo.\"1\"", + "result": ["one", "two", "three"] + }, + { + "expression": "foo.\"1\"[0]", + "result": "one" + }, + { + "expression": "foo.\"-1\"", + "result": "bar" + } + ] +} +] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/boolean.json b/vendor/mtdowling/jmespath.php/tests/compliance/boolean.json new file mode 100644 index 0000000..60635ac --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/boolean.json @@ -0,0 +1,275 @@ +[ + { + "given": { + "outer": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + } + }, + "cases": [ + { + "expression": "outer.foo || outer.bar", + "result": "foo" + }, + { + "expression": "outer.foo||outer.bar", + "result": "foo" + }, + { + "expression": "outer.bar || outer.baz", + "result": "bar" + }, + { + "expression": "outer.bar||outer.baz", + "result": "bar" + }, + { + "expression": "outer.bad || outer.foo", + "result": "foo" + }, + { + "expression": "outer.bad||outer.foo", + "result": "foo" + }, + { + "expression": "outer.foo || outer.bad", + "result": "foo" + }, + { + "expression": "outer.foo||outer.bad", + "result": "foo" + }, + { + "expression": "outer.bad || outer.alsobad", + "result": null + }, + { + "expression": "outer.bad||outer.alsobad", + "result": null + } + ] + }, + { + "given": { + "outer": { + "foo": "foo", + "bool": false, + "empty_list": [], + "empty_string": "" + } + }, + "cases": [ + { + "expression": "outer.empty_string || outer.foo", + "result": "foo" + }, + { + "expression": "outer.nokey || outer.bool || outer.empty_list || outer.empty_string || outer.foo", + "result": "foo" + } + ] + }, + { + "given": { + "True": true, + "False": false, + "Number": 5, + "EmptyList": [], + "Zero": 0 + }, + "cases": [ + { + "expression": "True && False", + "result": false + }, + { + "expression": "False && True", + "result": false + }, + { + "expression": "True && True", + "result": true + }, + { + "expression": "False && False", + "result": false + }, + { + "expression": "True && Number", + "result": 5 + }, + { + "expression": "Number && True", + "result": true + }, + { + "expression": "Number && False", + "result": false + }, + { + "expression": "Number && EmptyList", + "result": [] + }, + { + "expression": "Number && True", + "result": true + }, + { + "expression": "EmptyList && True", + "result": [] + }, + { + "expression": "EmptyList && False", + "result": [] + }, + { + "expression": "True || False", + "result": true + }, + { + "expression": "True || True", + "result": true + }, + { + "expression": "False || True", + "result": true + }, + { + "expression": "False || False", + "result": false + }, + { + "expression": "Number || EmptyList", + "result": 5 + }, + { + "expression": "Number || True", + "result": 5 + }, + { + "expression": "Number || True && False", + "result": 5 + }, + { + "expression": "(Number || True) && False", + "result": false + }, + { + "expression": "Number || (True && False)", + "result": 5 + }, + { + "expression": "!True", + "result": false + }, + { + "expression": "!False", + "result": true + }, + { + "expression": "!Number", + "result": false + }, + { + "expression": "!EmptyList", + "result": true + }, + { + "expression": "True && !False", + "result": true + }, + { + "expression": "True && !EmptyList", + "result": true + }, + { + "expression": "!False && !EmptyList", + "result": true + }, + { + "expression": "!(True && False)", + "result": true + }, + { + "expression": "!Zero", + "result": false + }, + { + "expression": "!!Zero", + "result": true + } + ] + }, + { + "given": { + "one": 1, + "two": 2, + "three": 3, + "emptylist": [], + "boolvalue": false + }, + "cases": [ + { + "expression": "one < two", + "result": true + }, + { + "expression": "one <= two", + "result": true + }, + { + "expression": "one == one", + "result": true + }, + { + "expression": "one == two", + "result": false + }, + { + "expression": "one > two", + "result": false + }, + { + "expression": "one >= two", + "result": false + }, + { + "expression": "one != two", + "result": true + }, + { + "expression": "emptylist < one", + "result": null + }, + { + "expression": "emptylist < nullvalue", + "result": null + }, + { + "expression": "emptylist < boolvalue", + "result": null + }, + { + "expression": "one < boolvalue", + "result": null + }, + { + "expression": "one < two && three > one", + "result": true + }, + { + "expression": "one < two || three > one", + "result": true + }, + { + "expression": "one < two || three < one", + "result": true + }, + { + "expression": "two < one || three < one", + "result": false + } + ] + } +] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/current.json b/vendor/mtdowling/jmespath.php/tests/compliance/current.json new file mode 100644 index 0000000..0c26248 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/current.json @@ -0,0 +1,25 @@ +[ + { + "given": { + "foo": [{"name": "a"}, {"name": "b"}], + "bar": {"baz": "qux"} + }, + "cases": [ + { + "expression": "@", + "result": { + "foo": [{"name": "a"}, {"name": "b"}], + "bar": {"baz": "qux"} + } + }, + { + "expression": "@.bar", + "result": {"baz": "qux"} + }, + { + "expression": "@.foo[0]", + "result": {"name": "a"} + } + ] + } +] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/escape.json b/vendor/mtdowling/jmespath.php/tests/compliance/escape.json new file mode 100644 index 0000000..4a62d95 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/escape.json @@ -0,0 +1,46 @@ +[{ + "given": { + "foo.bar": "dot", + "foo bar": "space", + "foo\nbar": "newline", + "foo\"bar": "doublequote", + "c:\\\\windows\\path": "windows", + "/unix/path": "unix", + "\"\"\"": "threequotes", + "bar": {"baz": "qux"} + }, + "cases": [ + { + "expression": "\"foo.bar\"", + "result": "dot" + }, + { + "expression": "\"foo bar\"", + "result": "space" + }, + { + "expression": "\"foo\\nbar\"", + "result": "newline" + }, + { + "expression": "\"foo\\\"bar\"", + "result": "doublequote" + }, + { + "expression": "\"c:\\\\\\\\windows\\\\path\"", + "result": "windows" + }, + { + "expression": "\"/unix/path\"", + "result": "unix" + }, + { + "expression": "\"\\\"\\\"\\\"\"", + "result": "threequotes" + }, + { + "expression": "\"bar\".\"baz\"", + "result": "qux" + } + ] +}] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/filters.json b/vendor/mtdowling/jmespath.php/tests/compliance/filters.json new file mode 100644 index 0000000..c4fa26a --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/filters.json @@ -0,0 +1,512 @@ +[ + { + "given": {"foo": [{"name": "a"}, {"name": "b"}]}, + "cases": [ + { + "comment": "Matching a literal", + "expression": "foo[?name == 'a']", + "result": [{"name": "a"}] + } + ] + }, + { + "given": {"foo": [0, 1], "bar": [2, 3]}, + "cases": [ + { + "comment": "Matching a literal", + "expression": "*[?[0] == `0`]", + "result": [[], []] + } + ] + }, + { + "given": {"foo": [{"first": "foo", "last": "bar"}, + {"first": "foo", "last": "foo"}, + {"first": "foo", "last": "baz"}]}, + "cases": [ + { + "comment": "Matching an expression", + "expression": "foo[?first == last]", + "result": [{"first": "foo", "last": "foo"}] + }, + { + "comment": "Verify projection created from filter", + "expression": "foo[?first == last].first", + "result": ["foo"] + } + ] + }, + { + "given": {"foo": [{"age": 20}, + {"age": 25}, + {"age": 30}]}, + "cases": [ + { + "comment": "Greater than with a number", + "expression": "foo[?age > `25`]", + "result": [{"age": 30}] + }, + { + "expression": "foo[?age >= `25`]", + "result": [{"age": 25}, {"age": 30}] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?age > `30`]", + "result": [] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?age < `25`]", + "result": [{"age": 20}] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?age <= `25`]", + "result": [{"age": 20}, {"age": 25}] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?age < `20`]", + "result": [] + }, + { + "expression": "foo[?age == `20`]", + "result": [{"age": 20}] + }, + { + "expression": "foo[?age != `20`]", + "result": [{"age": 25}, {"age": 30}] + } + ] + }, + { + "given": {"foo": [{"weight": 33.3}, + {"weight": 44.4}, + {"weight": 55.5}]}, + "cases": [ + { + "comment": "Greater than with a number", + "expression": "foo[?weight > `44.4`]", + "result": [{"weight": 55.5}] + }, + { + "expression": "foo[?weight >= `44.4`]", + "result": [{"weight": 44.4}, {"weight": 55.5}] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?weight > `55.5`]", + "result": [] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?weight < `44.4`]", + "result": [{"weight": 33.3}] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?weight <= `44.4`]", + "result": [{"weight": 33.3}, {"weight": 44.4}] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?weight < `33.3`]", + "result": [] + }, + { + "expression": "foo[?weight == `33.3`]", + "result": [{"weight": 33.3}] + }, + { + "expression": "foo[?weight != `33.3`]", + "result": [{"weight": 44.4}, {"weight": 55.5}] + } + ] + }, + { + "given": {"foo": [{"top": {"name": "a"}}, + {"top": {"name": "b"}}]}, + "cases": [ + { + "comment": "Filter with subexpression", + "expression": "foo[?top.name == 'a']", + "result": [{"top": {"name": "a"}}] + } + ] + }, + { + "given": {"foo": [{"top": {"first": "foo", "last": "bar"}}, + {"top": {"first": "foo", "last": "foo"}}, + {"top": {"first": "foo", "last": "baz"}}]}, + "cases": [ + { + "comment": "Matching an expression", + "expression": "foo[?top.first == top.last]", + "result": [{"top": {"first": "foo", "last": "foo"}}] + }, + { + "comment": "Matching a JSON array", + "expression": "foo[?top == `{\"first\": \"foo\", \"last\": \"bar\"}`]", + "result": [{"top": {"first": "foo", "last": "bar"}}] + } + ] + }, + { + "given": {"foo": [ + {"key": true}, + {"key": false}, + {"key": 0}, + {"key": 1}, + {"key": [0]}, + {"key": {"bar": [0]}}, + {"key": null}, + {"key": [1]}, + {"key": {"a":2}} + ]}, + "cases": [ + { + "expression": "foo[?key == `true`]", + "result": [{"key": true}] + }, + { + "expression": "foo[?key == `false`]", + "result": [{"key": false}] + }, + { + "expression": "foo[?key == `0`]", + "result": [{"key": 0}] + }, + { + "expression": "foo[?key == `1`]", + "result": [{"key": 1}] + }, + { + "expression": "foo[?key == `[0]`]", + "result": [{"key": [0]}] + }, + { + "expression": "foo[?key == `{\"bar\": [0]}`]", + "result": [{"key": {"bar": [0]}}] + }, + { + "expression": "foo[?key == `null`]", + "result": [{"key": null}] + }, + { + "expression": "foo[?key == `[1]`]", + "result": [{"key": [1]}] + }, + { + "expression": "foo[?key == `{\"a\":2}`]", + "result": [{"key": {"a":2}}] + }, + { + "expression": "foo[?`true` == key]", + "result": [{"key": true}] + }, + { + "expression": "foo[?`false` == key]", + "result": [{"key": false}] + }, + { + "expression": "foo[?`0` == key]", + "result": [{"key": 0}] + }, + { + "expression": "foo[?`1` == key]", + "result": [{"key": 1}] + }, + { + "expression": "foo[?`[0]` == key]", + "result": [{"key": [0]}] + }, + { + "expression": "foo[?`{\"bar\": [0]}` == key]", + "result": [{"key": {"bar": [0]}}] + }, + { + "expression": "foo[?`null` == key]", + "result": [{"key": null}] + }, + { + "expression": "foo[?`[1]` == key]", + "result": [{"key": [1]}] + }, + { + "expression": "foo[?`{\"a\":2}` == key]", + "result": [{"key": {"a":2}}] + }, + { + "expression": "foo[?key != `true`]", + "result": [{"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?key != `false`]", + "result": [{"key": true}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?key != `0`]", + "result": [{"key": true}, {"key": false}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?key != `1`]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?key != `null`]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?key != `[1]`]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": {"a":2}}] + }, + { + "expression": "foo[?key != `{\"a\":2}`]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}] + }, + { + "expression": "foo[?`true` != key]", + "result": [{"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?`false` != key]", + "result": [{"key": true}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?`0` != key]", + "result": [{"key": true}, {"key": false}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?`1` != key]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?`null` != key]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?`[1]` != key]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": {"a":2}}] + }, + { + "expression": "foo[?`{\"a\":2}` != key]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}] + } + ] + }, + { + "given": {"reservations": [ + {"instances": [ + {"foo": 1, "bar": 2}, {"foo": 1, "bar": 3}, + {"foo": 1, "bar": 2}, {"foo": 2, "bar": 1}]}]}, + "cases": [ + { + "expression": "reservations[].instances[?bar==`1`]", + "result": [[{"foo": 2, "bar": 1}]] + }, + { + "expression": "reservations[*].instances[?bar==`1`]", + "result": [[{"foo": 2, "bar": 1}]] + }, + { + "expression": "reservations[].instances[?bar==`1`][]", + "result": [{"foo": 2, "bar": 1}] + } + ] + }, + { + "given": { + "baz": "other", + "foo": [ + {"bar": 1}, {"bar": 2}, {"bar": 3}, {"bar": 4}, {"bar": 1, "baz": 2} + ] + }, + "cases": [ + { + "expression": "foo[?bar==`1`].bar[0]", + "result": [] + } + ] + }, + { + "given": { + "foo": [ + {"a": 1, "b": {"c": "x"}}, + {"a": 1, "b": {"c": "y"}}, + {"a": 1, "b": {"c": "z"}}, + {"a": 2, "b": {"c": "z"}}, + {"a": 1, "baz": 2} + ] + }, + "cases": [ + { + "expression": "foo[?a==`1`].b.c", + "result": ["x", "y", "z"] + } + ] + }, + { + "given": {"foo": [{"name": "a"}, {"name": "b"}, {"name": "c"}]}, + "cases": [ + { + "comment": "Filter with or expression", + "expression": "foo[?name == 'a' || name == 'b']", + "result": [{"name": "a"}, {"name": "b"}] + }, + { + "expression": "foo[?name == 'a' || name == 'e']", + "result": [{"name": "a"}] + }, + { + "expression": "foo[?name == 'a' || name == 'b' || name == 'c']", + "result": [{"name": "a"}, {"name": "b"}, {"name": "c"}] + } + ] + }, + { + "given": {"foo": [{"a": 1, "b": 2}, {"a": 1, "b": 3}]}, + "cases": [ + { + "comment": "Filter with and expression", + "expression": "foo[?a == `1` && b == `2`]", + "result": [{"a": 1, "b": 2}] + }, + { + "expression": "foo[?a == `1` && b == `4`]", + "result": [] + } + ] + }, + { + "given": {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]}, + "cases": [ + { + "comment": "Filter with Or and And expressions", + "expression": "foo[?c == `3` || a == `1` && b == `4`]", + "result": [{"a": 1, "b": 2, "c": 3}] + }, + { + "expression": "foo[?b == `2` || a == `3` && b == `4`]", + "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] + }, + { + "expression": "foo[?a == `3` && b == `4` || b == `2`]", + "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] + }, + { + "expression": "foo[?(a == `3` && b == `4`) || b == `2`]", + "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] + }, + { + "expression": "foo[?((a == `3` && b == `4`)) || b == `2`]", + "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] + }, + { + "expression": "foo[?a == `3` && (b == `4` || b == `2`)]", + "result": [{"a": 3, "b": 4}] + }, + { + "expression": "foo[?a == `3` && ((b == `4` || b == `2`))]", + "result": [{"a": 3, "b": 4}] + } + ] + }, + { + "given": {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]}, + "cases": [ + { + "comment": "Verify precedence of or/and expressions", + "expression": "foo[?a == `1` || b ==`2` && c == `5`]", + "result": [{"a": 1, "b": 2, "c": 3}] + }, + { + "comment": "Parentheses can alter precedence", + "expression": "foo[?(a == `1` || b ==`2`) && c == `5`]", + "result": [] + }, + { + "comment": "Not expressions combined with and/or", + "expression": "foo[?!(a == `1` || b ==`2`)]", + "result": [{"a": 3, "b": 4}] + } + ] + }, + { + "given": { + "foo": [ + {"key": true}, + {"key": false}, + {"key": []}, + {"key": {}}, + {"key": [0]}, + {"key": {"a": "b"}}, + {"key": 0}, + {"key": 1}, + {"key": null}, + {"notkey": true} + ] + }, + "cases": [ + { + "comment": "Unary filter expression", + "expression": "foo[?key]", + "result": [ + {"key": true}, {"key": [0]}, {"key": {"a": "b"}}, + {"key": 0}, {"key": 1} + ] + }, + { + "comment": "Unary not filter expression", + "expression": "foo[?!key]", + "result": [ + {"key": false}, {"key": []}, {"key": {}}, + {"key": null}, {"notkey": true} + ] + }, + { + "comment": "Equality with null RHS", + "expression": "foo[?key == `null`]", + "result": [ + {"key": null}, {"notkey": true} + ] + } + ] + }, + { + "given": { + "foo": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + "cases": [ + { + "comment": "Using @ in a filter expression", + "expression": "foo[?@ < `5`]", + "result": [0, 1, 2, 3, 4] + }, + { + "comment": "Using @ in a filter expression", + "expression": "foo[?`5` > @]", + "result": [0, 1, 2, 3, 4] + }, + { + "comment": "Using @ in a filter expression", + "expression": "foo[?@ == @]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + } + ] + } +] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/functions.json b/vendor/mtdowling/jmespath.php/tests/compliance/functions.json new file mode 100644 index 0000000..2a07416 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/functions.json @@ -0,0 +1,837 @@ +[{ + "given": + { + "foo": -1, + "zero": 0, + "numbers": [-1, 3, 4, 5], + "array": [-1, 3, 4, 5, "a", "100"], + "strings": ["a", "b", "c"], + "decimals": [1.01, 1.2, -1.5], + "str": "Str", + "false": false, + "empty_list": [], + "empty_hash": {}, + "objects": {"foo": "bar", "bar": "baz"}, + "null_key": null + }, + "cases": [ + { + "expression": "abs(foo)", + "result": 1 + }, + { + "expression": "abs(foo)", + "result": 1 + }, + { + "expression": "abs(str)", + "error": "invalid-type" + }, + { + "expression": "abs(array[1])", + "result": 3 + }, + { + "expression": "abs(array[1])", + "result": 3 + }, + { + "expression": "abs(`false`)", + "error": "invalid-type" + }, + { + "expression": "abs(`-24`)", + "result": 24 + }, + { + "expression": "abs(`-24`)", + "result": 24 + }, + { + "expression": "abs(`1`, `2`)", + "error": "invalid-arity" + }, + { + "expression": "abs()", + "error": "invalid-arity" + }, + { + "expression": "unknown_function(`1`, `2`)", + "error": "unknown-function" + }, + { + "expression": "avg(numbers)", + "result": 2.75 + }, + { + "expression": "avg(array)", + "error": "invalid-type" + }, + { + "expression": "avg('abc')", + "error": "invalid-type" + }, + { + "expression": "avg(foo)", + "error": "invalid-type" + }, + { + "expression": "avg(@)", + "error": "invalid-type" + }, + { + "expression": "avg(strings)", + "error": "invalid-type" + }, + { + "expression": "avg(empty_list)", + "result": null + }, + { + "expression": "ceil(`1.2`)", + "result": 2 + }, + { + "expression": "ceil(decimals[0])", + "result": 2 + }, + { + "expression": "ceil(decimals[1])", + "result": 2 + }, + { + "expression": "ceil(decimals[2])", + "result": -1 + }, + { + "expression": "ceil('string')", + "error": "invalid-type" + }, + { + "expression": "contains('abc', 'a')", + "result": true + }, + { + "expression": "contains('abc', 'd')", + "result": false + }, + { + "expression": "contains(`false`, 'd')", + "error": "invalid-type" + }, + { + "expression": "contains(strings, 'a')", + "result": true + }, + { + "expression": "contains(decimals, `1.2`)", + "result": true + }, + { + "expression": "contains(decimals, `false`)", + "result": false + }, + { + "expression": "ends_with(str, 'r')", + "result": true + }, + { + "expression": "ends_with(str, 'tr')", + "result": true + }, + { + "expression": "ends_with(str, 'Str')", + "result": true + }, + { + "expression": "ends_with(str, 'SStr')", + "result": false + }, + { + "expression": "ends_with(str, 'foo')", + "result": false + }, + { + "expression": "ends_with(str, `0`)", + "error": "invalid-type" + }, + { + "expression": "floor(`1.2`)", + "result": 1 + }, + { + "expression": "floor('string')", + "error": "invalid-type" + }, + { + "expression": "floor(decimals[0])", + "result": 1 + }, + { + "expression": "floor(foo)", + "result": -1 + }, + { + "expression": "floor(str)", + "error": "invalid-type" + }, + { + "expression": "length('abc')", + "result": 3 + }, + { + "expression": "length('✓foo')", + "result": 4 + }, + { + "expression": "length('')", + "result": 0 + }, + { + "expression": "length(@)", + "result": 12 + }, + { + "expression": "length(strings[0])", + "result": 1 + }, + { + "expression": "length(str)", + "result": 3 + }, + { + "expression": "length(array)", + "result": 6 + }, + { + "expression": "length(objects)", + "result": 2 + }, + { + "expression": "length(`false`)", + "error": "invalid-type" + }, + { + "expression": "length(foo)", + "error": "invalid-type" + }, + { + "expression": "length(strings[0])", + "result": 1 + }, + { + "expression": "max(numbers)", + "result": 5 + }, + { + "expression": "max(decimals)", + "result": 1.2 + }, + { + "expression": "max(strings)", + "result": "c" + }, + { + "expression": "max(abc)", + "error": "invalid-type" + }, + { + "expression": "max(array)", + "error": "invalid-type" + }, + { + "expression": "max(decimals)", + "result": 1.2 + }, + { + "expression": "max(empty_list)", + "result": null + }, + { + "expression": "merge(`{}`)", + "result": {} + }, + { + "expression": "merge(`{}`, `{}`)", + "result": {} + }, + { + "expression": "merge(`{\"a\": 1}`, `{\"b\": 2}`)", + "result": {"a": 1, "b": 2} + }, + { + "expression": "merge(`{\"a\": 1}`, `{\"a\": 2}`)", + "result": {"a": 2} + }, + { + "expression": "merge(`{\"a\": 1, \"b\": 2}`, `{\"a\": 2, \"c\": 3}`, `{\"d\": 4}`)", + "result": {"a": 2, "b": 2, "c": 3, "d": 4} + }, + { + "expression": "min(numbers)", + "result": -1 + }, + { + "expression": "min(decimals)", + "result": -1.5 + }, + { + "expression": "min(abc)", + "error": "invalid-type" + }, + { + "expression": "min(array)", + "error": "invalid-type" + }, + { + "expression": "min(empty_list)", + "result": null + }, + { + "expression": "min(decimals)", + "result": -1.5 + }, + { + "expression": "min(strings)", + "result": "a" + }, + { + "expression": "type('abc')", + "result": "string" + }, + { + "expression": "type(`1.0`)", + "result": "number" + }, + { + "expression": "type(`2`)", + "result": "number" + }, + { + "expression": "type(`true`)", + "result": "boolean" + }, + { + "expression": "type(`false`)", + "result": "boolean" + }, + { + "expression": "type(`null`)", + "result": "null" + }, + { + "expression": "type(`[0]`)", + "result": "array" + }, + { + "expression": "type(`{\"a\": \"b\"}`)", + "result": "object" + }, + { + "expression": "type(@)", + "result": "object" + }, + { + "expression": "sort(keys(objects))", + "result": ["bar", "foo"] + }, + { + "expression": "keys(foo)", + "error": "invalid-type" + }, + { + "expression": "keys(strings)", + "error": "invalid-type" + }, + { + "expression": "keys(`false`)", + "error": "invalid-type" + }, + { + "expression": "sort(values(objects))", + "result": ["bar", "baz"] + }, + { + "expression": "keys(empty_hash)", + "result": [] + }, + { + "expression": "values(foo)", + "error": "invalid-type" + }, + { + "expression": "join(', ', strings)", + "result": "a, b, c" + }, + { + "expression": "join(', ', strings)", + "result": "a, b, c" + }, + { + "expression": "join(',', `[\"a\", \"b\"]`)", + "result": "a,b" + }, + { + "expression": "join(',', `[\"a\", 0]`)", + "error": "invalid-type" + }, + { + "expression": "join(', ', str)", + "error": "invalid-type" + }, + { + "expression": "join('|', strings)", + "result": "a|b|c" + }, + { + "expression": "join(`2`, strings)", + "error": "invalid-type" + }, + { + "expression": "join('|', decimals)", + "error": "invalid-type" + }, + { + "expression": "join('|', decimals[].to_string(@))", + "result": "1.01|1.2|-1.5" + }, + { + "expression": "join('|', empty_list)", + "result": "" + }, + { + "expression": "reverse(numbers)", + "result": [5, 4, 3, -1] + }, + { + "expression": "reverse(array)", + "result": ["100", "a", 5, 4, 3, -1] + }, + { + "expression": "reverse(`[]`)", + "result": [] + }, + { + "expression": "reverse('')", + "result": "" + }, + { + "expression": "reverse('hello world')", + "result": "dlrow olleh" + }, + { + "expression": "starts_with(str, 'S')", + "result": true + }, + { + "expression": "starts_with(str, 'St')", + "result": true + }, + { + "expression": "starts_with(str, 'Str')", + "result": true + }, + { + "expression": "starts_with(str, 'String')", + "result": false + }, + { + "expression": "starts_with(str, `0`)", + "error": "invalid-type" + }, + { + "expression": "sum(numbers)", + "result": 11 + }, + { + "expression": "sum(decimals)", + "result": 0.71 + }, + { + "expression": "sum(array)", + "error": "invalid-type" + }, + { + "expression": "sum(array[].to_number(@))", + "result": 111 + }, + { + "expression": "sum(`[]`)", + "result": 0 + }, + { + "expression": "to_array('foo')", + "result": ["foo"] + }, + { + "expression": "to_array(`0`)", + "result": [0] + }, + { + "expression": "to_array(objects)", + "result": [{"foo": "bar", "bar": "baz"}] + }, + { + "expression": "to_array(`[1, 2, 3]`)", + "result": [1, 2, 3] + }, + { + "expression": "to_array(false)", + "result": [false] + }, + { + "expression": "to_string('foo')", + "result": "foo" + }, + { + "expression": "to_string(`1.2`)", + "result": "1.2" + }, + { + "expression": "to_string(`[0, 1]`)", + "result": "[0,1]" + }, + { + "expression": "to_number('1.0')", + "result": 1.0 + }, + { + "expression": "to_number('1.1')", + "result": 1.1 + }, + { + "expression": "to_number('4')", + "result": 4 + }, + { + "expression": "to_number('notanumber')", + "result": null + }, + { + "expression": "to_number(`false`)", + "result": null + }, + { + "expression": "to_number(`null`)", + "result": null + }, + { + "expression": "to_number(`[0]`)", + "result": null + }, + { + "expression": "to_number(`{\"foo\": 0}`)", + "result": null + }, + { + "expression": "\"to_string\"(`1.0`)", + "error": "syntax" + }, + { + "expression": "sort(numbers)", + "result": [-1, 3, 4, 5] + }, + { + "expression": "sort(strings)", + "result": ["a", "b", "c"] + }, + { + "expression": "sort(decimals)", + "result": [-1.5, 1.01, 1.2] + }, + { + "expression": "sort(array)", + "error": "invalid-type" + }, + { + "expression": "sort(abc)", + "error": "invalid-type" + }, + { + "expression": "sort(empty_list)", + "result": [] + }, + { + "expression": "sort(@)", + "error": "invalid-type" + }, + { + "expression": "not_null(unknown_key, str)", + "result": "Str" + }, + { + "expression": "not_null(unknown_key, foo.bar, empty_list, str)", + "result": [] + }, + { + "expression": "not_null(unknown_key, null_key, empty_list, str)", + "result": [] + }, + { + "expression": "not_null(all, expressions, are_null)", + "result": null + }, + { + "expression": "not_null()", + "error": "invalid-arity" + }, + { + "comment": "function projection on single arg function", + "expression": "numbers[].to_string(@)", + "result": ["-1", "3", "4", "5"] + }, + { + "comment": "function projection on single arg function", + "expression": "array[].to_number(@)", + "result": [-1, 3, 4, 5, 100] + } + ] +}, { + "given": + { + "foo": [ + {"b": "b", "a": "a"}, + {"c": "c", "b": "b"}, + {"d": "d", "c": "c"}, + {"e": "e", "d": "d"}, + {"f": "f", "e": "e"} + ] + }, + "cases": [ + { + "comment": "function projection on variadic function", + "expression": "foo[].not_null(f, e, d, c, b, a)", + "result": ["b", "c", "d", "e", "f"] + } + ] +}, { + "given": + { + "people": [ + {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"}, + {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"}, + {"age": 30, "age_str": "30", "bool": true, "name": "c"}, + {"age": 50, "age_str": "50", "bool": false, "name": "d"}, + {"age": 10, "age_str": "10", "bool": true, "name": 3} + ] + }, + "cases": [ + { + "comment": "sort by field expression", + "expression": "sort_by(people, &age)", + "result": [ + {"age": 10, "age_str": "10", "bool": true, "name": 3}, + {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"}, + {"age": 30, "age_str": "30", "bool": true, "name": "c"}, + {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"}, + {"age": 50, "age_str": "50", "bool": false, "name": "d"} + ] + }, + { + "expression": "sort_by(people, &age_str)", + "result": [ + {"age": 10, "age_str": "10", "bool": true, "name": 3}, + {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"}, + {"age": 30, "age_str": "30", "bool": true, "name": "c"}, + {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"}, + {"age": 50, "age_str": "50", "bool": false, "name": "d"} + ] + }, + { + "comment": "sort by function expression", + "expression": "sort_by(people, &to_number(age_str))", + "result": [ + {"age": 10, "age_str": "10", "bool": true, "name": 3}, + {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"}, + {"age": 30, "age_str": "30", "bool": true, "name": "c"}, + {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"}, + {"age": 50, "age_str": "50", "bool": false, "name": "d"} + ] + }, + { + "comment": "function projection on sort_by function", + "expression": "sort_by(people, &age)[].name", + "result": [3, "a", "c", "b", "d"] + }, + { + "expression": "sort_by(people, &extra)", + "error": "invalid-type" + }, + { + "expression": "sort_by(people, &bool)", + "error": "invalid-type" + }, + { + "expression": "sort_by(people, &name)", + "error": "invalid-type" + }, + { + "expression": "sort_by(people, name)", + "error": "invalid-type" + }, + { + "expression": "sort_by(people, &age)[].extra", + "result": ["foo", "bar"] + }, + { + "expression": "sort_by(`[]`, &age)", + "result": [] + }, + { + "expression": "max_by(people, &age)", + "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"} + }, + { + "expression": "max_by(people, &age_str)", + "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"} + }, + { + "expression": "max_by(people, &bool)", + "error": "invalid-type" + }, + { + "expression": "max_by(people, &extra)", + "error": "invalid-type" + }, + { + "expression": "max_by(people, &to_number(age_str))", + "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"} + }, + { + "expression": "max_by(`[]`, &age)", + "result": null + }, + { + "expression": "min_by(people, &age)", + "result": {"age": 10, "age_str": "10", "bool": true, "name": 3} + }, + { + "expression": "min_by(people, &age_str)", + "result": {"age": 10, "age_str": "10", "bool": true, "name": 3} + }, + { + "expression": "min_by(people, &bool)", + "error": "invalid-type" + }, + { + "expression": "min_by(people, &extra)", + "error": "invalid-type" + }, + { + "expression": "min_by(people, &to_number(age_str))", + "result": {"age": 10, "age_str": "10", "bool": true, "name": 3} + }, + { + "expression": "min_by(`[]`, &age)", + "result": null + } + ] +}, { + "given": + { + "people": [ + {"age": 10, "order": "1"}, + {"age": 10, "order": "2"}, + {"age": 10, "order": "3"}, + {"age": 10, "order": "4"}, + {"age": 10, "order": "5"}, + {"age": 10, "order": "6"}, + {"age": 10, "order": "7"}, + {"age": 10, "order": "8"}, + {"age": 10, "order": "9"}, + {"age": 10, "order": "10"}, + {"age": 10, "order": "11"} + ] + }, + "cases": [ + { + "comment": "stable sort order", + "expression": "sort_by(people, &age)", + "result": [ + {"age": 10, "order": "1"}, + {"age": 10, "order": "2"}, + {"age": 10, "order": "3"}, + {"age": 10, "order": "4"}, + {"age": 10, "order": "5"}, + {"age": 10, "order": "6"}, + {"age": 10, "order": "7"}, + {"age": 10, "order": "8"}, + {"age": 10, "order": "9"}, + {"age": 10, "order": "10"}, + {"age": 10, "order": "11"} + ] + } + ] +}, { + "given": + { + "people": [ + {"a": 10, "b": 1, "c": "z"}, + {"a": 10, "b": 2, "c": null}, + {"a": 10, "b": 3}, + {"a": 10, "b": 4, "c": "z"}, + {"a": 10, "b": 5, "c": null}, + {"a": 10, "b": 6}, + {"a": 10, "b": 7, "c": "z"}, + {"a": 10, "b": 8, "c": null}, + {"a": 10, "b": 9} + ], + "empty": [] + }, + "cases": [ + { + "expression": "map(&a, people)", + "result": [10, 10, 10, 10, 10, 10, 10, 10, 10] + }, + { + "expression": "map(&c, people)", + "result": ["z", null, null, "z", null, null, "z", null, null] + }, + { + "expression": "map(&a, badkey)", + "error": "invalid-type" + }, + { + "expression": "map(&foo, empty)", + "result": [] + } + ] +}, { + "given": { + "array": [ + { + "foo": {"bar": "yes1"} + }, + { + "foo": {"bar": "yes2"} + }, + { + "foo1": {"bar": "no"} + } + ]}, + "cases": [ + { + "expression": "map(&foo.bar, array)", + "result": ["yes1", "yes2", null] + }, + { + "expression": "map(&foo1.bar, array)", + "result": [null, null, "no"] + }, + { + "expression": "map(&foo.bar.baz, array)", + "result": [null, null, null] + } + ] +}, { + "given": { + "array": [[1, 2, 3, [4]], [5, 6, 7, [8, 9]]] + }, + "cases": [ + { + "expression": "map(&[], array)", + "result": [[1, 2, 3, 4], [5, 6, 7, 8, 9]] + } + ] +} +] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/identifiers.json b/vendor/mtdowling/jmespath.php/tests/compliance/identifiers.json new file mode 100644 index 0000000..7998a41 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/identifiers.json @@ -0,0 +1,1377 @@ +[ + { + "given": { + "__L": true + }, + "cases": [ + { + "expression": "__L", + "result": true + } + ] + }, + { + "given": { + "!\r": true + }, + "cases": [ + { + "expression": "\"!\\r\"", + "result": true + } + ] + }, + { + "given": { + "Y_1623": true + }, + "cases": [ + { + "expression": "Y_1623", + "result": true + } + ] + }, + { + "given": { + "x": true + }, + "cases": [ + { + "expression": "x", + "result": true + } + ] + }, + { + "given": { + "\tF\uCebb": true + }, + "cases": [ + { + "expression": "\"\\tF\\uCebb\"", + "result": true + } + ] + }, + { + "given": { + " \t": true + }, + "cases": [ + { + "expression": "\" \\t\"", + "result": true + } + ] + }, + { + "given": { + " ": true + }, + "cases": [ + { + "expression": "\" \"", + "result": true + } + ] + }, + { + "given": { + "v2": true + }, + "cases": [ + { + "expression": "v2", + "result": true + } + ] + }, + { + "given": { + "\t": true + }, + "cases": [ + { + "expression": "\"\\t\"", + "result": true + } + ] + }, + { + "given": { + "_X": true + }, + "cases": [ + { + "expression": "_X", + "result": true + } + ] + }, + { + "given": { + "\t4\ud9da\udd15": true + }, + "cases": [ + { + "expression": "\"\\t4\\ud9da\\udd15\"", + "result": true + } + ] + }, + { + "given": { + "v24_W": true + }, + "cases": [ + { + "expression": "v24_W", + "result": true + } + ] + }, + { + "given": { + "H": true + }, + "cases": [ + { + "expression": "\"H\"", + "result": true + } + ] + }, + { + "given": { + "\f": true + }, + "cases": [ + { + "expression": "\"\\f\"", + "result": true + } + ] + }, + { + "given": { + "E4": true + }, + "cases": [ + { + "expression": "\"E4\"", + "result": true + } + ] + }, + { + "given": { + "!": true + }, + "cases": [ + { + "expression": "\"!\"", + "result": true + } + ] + }, + { + "given": { + "tM": true + }, + "cases": [ + { + "expression": "tM", + "result": true + } + ] + }, + { + "given": { + " [": true + }, + "cases": [ + { + "expression": "\" [\"", + "result": true + } + ] + }, + { + "given": { + "R!": true + }, + "cases": [ + { + "expression": "\"R!\"", + "result": true + } + ] + }, + { + "given": { + "_6W": true + }, + "cases": [ + { + "expression": "_6W", + "result": true + } + ] + }, + { + "given": { + "\uaBA1\r": true + }, + "cases": [ + { + "expression": "\"\\uaBA1\\r\"", + "result": true + } + ] + }, + { + "given": { + "tL7": true + }, + "cases": [ + { + "expression": "tL7", + "result": true + } + ] + }, + { + "given": { + "<": true + }, + "cases": [ + { + "expression": "\">\"", + "result": true + } + ] + }, + { + "given": { + "hvu": true + }, + "cases": [ + { + "expression": "hvu", + "result": true + } + ] + }, + { + "given": { + "; !": true + }, + "cases": [ + { + "expression": "\"; !\"", + "result": true + } + ] + }, + { + "given": { + "hU": true + }, + "cases": [ + { + "expression": "hU", + "result": true + } + ] + }, + { + "given": { + "!I\n\/": true + }, + "cases": [ + { + "expression": "\"!I\\n\\/\"", + "result": true + } + ] + }, + { + "given": { + "\uEEbF": true + }, + "cases": [ + { + "expression": "\"\\uEEbF\"", + "result": true + } + ] + }, + { + "given": { + "U)\t": true + }, + "cases": [ + { + "expression": "\"U)\\t\"", + "result": true + } + ] + }, + { + "given": { + "fa0_9": true + }, + "cases": [ + { + "expression": "fa0_9", + "result": true + } + ] + }, + { + "given": { + "/": true + }, + "cases": [ + { + "expression": "\"/\"", + "result": true + } + ] + }, + { + "given": { + "Gy": true + }, + "cases": [ + { + "expression": "Gy", + "result": true + } + ] + }, + { + "given": { + "\b": true + }, + "cases": [ + { + "expression": "\"\\b\"", + "result": true + } + ] + }, + { + "given": { + "<": true + }, + "cases": [ + { + "expression": "\"<\"", + "result": true + } + ] + }, + { + "given": { + "\t": true + }, + "cases": [ + { + "expression": "\"\\t\"", + "result": true + } + ] + }, + { + "given": { + "\t&\\\r": true + }, + "cases": [ + { + "expression": "\"\\t&\\\\\\r\"", + "result": true + } + ] + }, + { + "given": { + "#": true + }, + "cases": [ + { + "expression": "\"#\"", + "result": true + } + ] + }, + { + "given": { + "B__": true + }, + "cases": [ + { + "expression": "B__", + "result": true + } + ] + }, + { + "given": { + "\nS \n": true + }, + "cases": [ + { + "expression": "\"\\nS \\n\"", + "result": true + } + ] + }, + { + "given": { + "Bp": true + }, + "cases": [ + { + "expression": "Bp", + "result": true + } + ] + }, + { + "given": { + ",\t;": true + }, + "cases": [ + { + "expression": "\",\\t;\"", + "result": true + } + ] + }, + { + "given": { + "B_q": true + }, + "cases": [ + { + "expression": "B_q", + "result": true + } + ] + }, + { + "given": { + "\/+\t\n\b!Z": true + }, + "cases": [ + { + "expression": "\"\\/+\\t\\n\\b!Z\"", + "result": true + } + ] + }, + { + "given": { + "\udadd\udfc7\\ueFAc": true + }, + "cases": [ + { + "expression": "\"\udadd\udfc7\\\\ueFAc\"", + "result": true + } + ] + }, + { + "given": { + ":\f": true + }, + "cases": [ + { + "expression": "\":\\f\"", + "result": true + } + ] + }, + { + "given": { + "\/": true + }, + "cases": [ + { + "expression": "\"\\/\"", + "result": true + } + ] + }, + { + "given": { + "_BW_6Hg_Gl": true + }, + "cases": [ + { + "expression": "_BW_6Hg_Gl", + "result": true + } + ] + }, + { + "given": { + "\udbcf\udc02": true + }, + "cases": [ + { + "expression": "\"\udbcf\udc02\"", + "result": true + } + ] + }, + { + "given": { + "zs1DC": true + }, + "cases": [ + { + "expression": "zs1DC", + "result": true + } + ] + }, + { + "given": { + "__434": true + }, + "cases": [ + { + "expression": "__434", + "result": true + } + ] + }, + { + "given": { + "\udb94\udd41": true + }, + "cases": [ + { + "expression": "\"\udb94\udd41\"", + "result": true + } + ] + }, + { + "given": { + "Z_5": true + }, + "cases": [ + { + "expression": "Z_5", + "result": true + } + ] + }, + { + "given": { + "z_M_": true + }, + "cases": [ + { + "expression": "z_M_", + "result": true + } + ] + }, + { + "given": { + "YU_2": true + }, + "cases": [ + { + "expression": "YU_2", + "result": true + } + ] + }, + { + "given": { + "_0": true + }, + "cases": [ + { + "expression": "_0", + "result": true + } + ] + }, + { + "given": { + "\b+": true + }, + "cases": [ + { + "expression": "\"\\b+\"", + "result": true + } + ] + }, + { + "given": { + "\"": true + }, + "cases": [ + { + "expression": "\"\\\"\"", + "result": true + } + ] + }, + { + "given": { + "D7": true + }, + "cases": [ + { + "expression": "D7", + "result": true + } + ] + }, + { + "given": { + "_62L": true + }, + "cases": [ + { + "expression": "_62L", + "result": true + } + ] + }, + { + "given": { + "\tK\t": true + }, + "cases": [ + { + "expression": "\"\\tK\\t\"", + "result": true + } + ] + }, + { + "given": { + "\n\\\f": true + }, + "cases": [ + { + "expression": "\"\\n\\\\\\f\"", + "result": true + } + ] + }, + { + "given": { + "I_": true + }, + "cases": [ + { + "expression": "I_", + "result": true + } + ] + }, + { + "given": { + "W_a0_": true + }, + "cases": [ + { + "expression": "W_a0_", + "result": true + } + ] + }, + { + "given": { + "BQ": true + }, + "cases": [ + { + "expression": "BQ", + "result": true + } + ] + }, + { + "given": { + "\tX$\uABBb": true + }, + "cases": [ + { + "expression": "\"\\tX$\\uABBb\"", + "result": true + } + ] + }, + { + "given": { + "Z9": true + }, + "cases": [ + { + "expression": "Z9", + "result": true + } + ] + }, + { + "given": { + "\b%\"\uda38\udd0f": true + }, + "cases": [ + { + "expression": "\"\\b%\\\"\uda38\udd0f\"", + "result": true + } + ] + }, + { + "given": { + "_F": true + }, + "cases": [ + { + "expression": "_F", + "result": true + } + ] + }, + { + "given": { + "!,": true + }, + "cases": [ + { + "expression": "\"!,\"", + "result": true + } + ] + }, + { + "given": { + "\"!": true + }, + "cases": [ + { + "expression": "\"\\\"!\"", + "result": true + } + ] + }, + { + "given": { + "Hh": true + }, + "cases": [ + { + "expression": "Hh", + "result": true + } + ] + }, + { + "given": { + "&": true + }, + "cases": [ + { + "expression": "\"&\"", + "result": true + } + ] + }, + { + "given": { + "9\r\\R": true + }, + "cases": [ + { + "expression": "\"9\\r\\\\R\"", + "result": true + } + ] + }, + { + "given": { + "M_k": true + }, + "cases": [ + { + "expression": "M_k", + "result": true + } + ] + }, + { + "given": { + "!\b\n\udb06\ude52\"\"": true + }, + "cases": [ + { + "expression": "\"!\\b\\n\udb06\ude52\\\"\\\"\"", + "result": true + } + ] + }, + { + "given": { + "6": true + }, + "cases": [ + { + "expression": "\"6\"", + "result": true + } + ] + }, + { + "given": { + "_7": true + }, + "cases": [ + { + "expression": "_7", + "result": true + } + ] + }, + { + "given": { + "0": true + }, + "cases": [ + { + "expression": "\"0\"", + "result": true + } + ] + }, + { + "given": { + "\\8\\": true + }, + "cases": [ + { + "expression": "\"\\\\8\\\\\"", + "result": true + } + ] + }, + { + "given": { + "b7eo": true + }, + "cases": [ + { + "expression": "b7eo", + "result": true + } + ] + }, + { + "given": { + "xIUo9": true + }, + "cases": [ + { + "expression": "xIUo9", + "result": true + } + ] + }, + { + "given": { + "5": true + }, + "cases": [ + { + "expression": "\"5\"", + "result": true + } + ] + }, + { + "given": { + "?": true + }, + "cases": [ + { + "expression": "\"?\"", + "result": true + } + ] + }, + { + "given": { + "sU": true + }, + "cases": [ + { + "expression": "sU", + "result": true + } + ] + }, + { + "given": { + "VH2&H\\\/": true + }, + "cases": [ + { + "expression": "\"VH2&H\\\\\\/\"", + "result": true + } + ] + }, + { + "given": { + "_C": true + }, + "cases": [ + { + "expression": "_C", + "result": true + } + ] + }, + { + "given": { + "_": true + }, + "cases": [ + { + "expression": "_", + "result": true + } + ] + }, + { + "given": { + "<\t": true + }, + "cases": [ + { + "expression": "\"<\\t\"", + "result": true + } + ] + }, + { + "given": { + "\uD834\uDD1E": true + }, + "cases": [ + { + "expression": "\"\\uD834\\uDD1E\"", + "result": true + } + ] + } +] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/indices.json b/vendor/mtdowling/jmespath.php/tests/compliance/indices.json new file mode 100644 index 0000000..aa03b35 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/indices.json @@ -0,0 +1,346 @@ +[{ + "given": + {"foo": {"bar": ["zero", "one", "two"]}}, + "cases": [ + { + "expression": "foo.bar[0]", + "result": "zero" + }, + { + "expression": "foo.bar[1]", + "result": "one" + }, + { + "expression": "foo.bar[2]", + "result": "two" + }, + { + "expression": "foo.bar[3]", + "result": null + }, + { + "expression": "foo.bar[-1]", + "result": "two" + }, + { + "expression": "foo.bar[-2]", + "result": "one" + }, + { + "expression": "foo.bar[-3]", + "result": "zero" + }, + { + "expression": "foo.bar[-4]", + "result": null + } + ] +}, +{ + "given": + {"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]}, + "cases": [ + { + "expression": "foo.bar", + "result": null + }, + { + "expression": "foo[0].bar", + "result": "one" + }, + { + "expression": "foo[1].bar", + "result": "two" + }, + { + "expression": "foo[2].bar", + "result": "three" + }, + { + "expression": "foo[3].notbar", + "result": "four" + }, + { + "expression": "foo[3].bar", + "result": null + }, + { + "expression": "foo[0]", + "result": {"bar": "one"} + }, + { + "expression": "foo[1]", + "result": {"bar": "two"} + }, + { + "expression": "foo[2]", + "result": {"bar": "three"} + }, + { + "expression": "foo[3]", + "result": {"notbar": "four"} + }, + { + "expression": "foo[4]", + "result": null + } + ] +}, +{ + "given": [ + "one", "two", "three" + ], + "cases": [ + { + "expression": "[0]", + "result": "one" + }, + { + "expression": "[1]", + "result": "two" + }, + { + "expression": "[2]", + "result": "three" + }, + { + "expression": "[-1]", + "result": "three" + }, + { + "expression": "[-2]", + "result": "two" + }, + { + "expression": "[-3]", + "result": "one" + } + ] +}, +{ + "given": {"reservations": [ + {"instances": [{"foo": 1}, {"foo": 2}]} + ]}, + "cases": [ + { + "expression": "reservations[].instances[].foo", + "result": [1, 2] + }, + { + "expression": "reservations[].instances[].bar", + "result": [] + }, + { + "expression": "reservations[].notinstances[].foo", + "result": [] + }, + { + "expression": "reservations[].notinstances[].foo", + "result": [] + } + ] +}, +{ + "given": {"reservations": [{ + "instances": [ + {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]}, + {"foo": [{"bar": 5}, {"bar": 6}, {"notbar": [7]}, {"bar": 8}]}, + {"foo": "bar"}, + {"notfoo": [{"bar": 20}, {"bar": 21}, {"notbar": [7]}, {"bar": 22}]}, + {"bar": [{"baz": [1]}, {"baz": [2]}, {"baz": [3]}, {"baz": [4]}]}, + {"baz": [{"baz": [1, 2]}, {"baz": []}, {"baz": []}, {"baz": [3, 4]}]}, + {"qux": [{"baz": []}, {"baz": [1, 2, 3]}, {"baz": [4]}, {"baz": []}]} + ], + "otherkey": {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]} + }, { + "instances": [ + {"a": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]}, + {"b": [{"bar": 5}, {"bar": 6}, {"notbar": [7]}, {"bar": 8}]}, + {"c": "bar"}, + {"notfoo": [{"bar": 23}, {"bar": 24}, {"notbar": [7]}, {"bar": 25}]}, + {"qux": [{"baz": []}, {"baz": [1, 2, 3]}, {"baz": [4]}, {"baz": []}]} + ], + "otherkey": {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]} + } + ]}, + "cases": [ + { + "expression": "reservations[].instances[].foo[].bar", + "result": [1, 2, 4, 5, 6, 8] + }, + { + "expression": "reservations[].instances[].foo[].baz", + "result": [] + }, + { + "expression": "reservations[].instances[].notfoo[].bar", + "result": [20, 21, 22, 23, 24, 25] + }, + { + "expression": "reservations[].instances[].notfoo[].notbar", + "result": [[7], [7]] + }, + { + "expression": "reservations[].notinstances[].foo", + "result": [] + }, + { + "expression": "reservations[].instances[].foo[].notbar", + "result": [3, [7]] + }, + { + "expression": "reservations[].instances[].bar[].baz", + "result": [[1], [2], [3], [4]] + }, + { + "expression": "reservations[].instances[].baz[].baz", + "result": [[1, 2], [], [], [3, 4]] + }, + { + "expression": "reservations[].instances[].qux[].baz", + "result": [[], [1, 2, 3], [4], [], [], [1, 2, 3], [4], []] + }, + { + "expression": "reservations[].instances[].qux[].baz[]", + "result": [1, 2, 3, 4, 1, 2, 3, 4] + } + ] +}, +{ + "given": { + "foo": [ + [["one", "two"], ["three", "four"]], + [["five", "six"], ["seven", "eight"]], + [["nine"], ["ten"]] + ] + }, + "cases": [ + { + "expression": "foo[]", + "result": [["one", "two"], ["three", "four"], ["five", "six"], + ["seven", "eight"], ["nine"], ["ten"]] + }, + { + "expression": "foo[][0]", + "result": ["one", "three", "five", "seven", "nine", "ten"] + }, + { + "expression": "foo[][1]", + "result": ["two", "four", "six", "eight"] + }, + { + "expression": "foo[][0][0]", + "result": [] + }, + { + "expression": "foo[][2][2]", + "result": [] + }, + { + "expression": "foo[][0][0][100]", + "result": [] + } + ] +}, +{ + "given": { + "foo": [{ + "bar": [ + { + "qux": 2, + "baz": 1 + }, + { + "qux": 4, + "baz": 3 + } + ] + }, + { + "bar": [ + { + "qux": 6, + "baz": 5 + }, + { + "qux": 8, + "baz": 7 + } + ] + } + ] + }, + "cases": [ + { + "expression": "foo", + "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, + {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] + }, + { + "expression": "foo[]", + "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, + {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] + }, + { + "expression": "foo[].bar", + "result": [[{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}], + [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]] + }, + { + "expression": "foo[].bar[]", + "result": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}, + {"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}] + }, + { + "expression": "foo[].bar[].baz", + "result": [1, 3, 5, 7] + } + ] +}, +{ + "given": { + "string": "string", + "hash": {"foo": "bar", "bar": "baz"}, + "number": 23, + "nullvalue": null + }, + "cases": [ + { + "expression": "string[]", + "result": null + }, + { + "expression": "hash[]", + "result": null + }, + { + "expression": "number[]", + "result": null + }, + { + "expression": "nullvalue[]", + "result": null + }, + { + "expression": "string[].foo", + "result": null + }, + { + "expression": "hash[].foo", + "result": null + }, + { + "expression": "number[].foo", + "result": null + }, + { + "expression": "nullvalue[].foo", + "result": null + }, + { + "expression": "nullvalue[].foo[].bar", + "result": null + } + ] +} +] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/literal.json b/vendor/mtdowling/jmespath.php/tests/compliance/literal.json new file mode 100644 index 0000000..b5ddbed --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/literal.json @@ -0,0 +1,200 @@ +[ + { + "given": { + "foo": [{"name": "a"}, {"name": "b"}], + "bar": {"baz": "qux"} + }, + "cases": [ + { + "expression": "`\"foo\"`", + "result": "foo" + }, + { + "comment": "Interpret escaped unicode.", + "expression": "`\"\\u03a6\"`", + "result": "Φ" + }, + { + "expression": "`\"✓\"`", + "result": "✓" + }, + { + "expression": "`[1, 2, 3]`", + "result": [1, 2, 3] + }, + { + "expression": "`{\"a\": \"b\"}`", + "result": {"a": "b"} + }, + { + "expression": "`true`", + "result": true + }, + { + "expression": "`false`", + "result": false + }, + { + "expression": "`null`", + "result": null + }, + { + "expression": "`0`", + "result": 0 + }, + { + "expression": "`1`", + "result": 1 + }, + { + "expression": "`2`", + "result": 2 + }, + { + "expression": "`3`", + "result": 3 + }, + { + "expression": "`4`", + "result": 4 + }, + { + "expression": "`5`", + "result": 5 + }, + { + "expression": "`6`", + "result": 6 + }, + { + "expression": "`7`", + "result": 7 + }, + { + "expression": "`8`", + "result": 8 + }, + { + "expression": "`9`", + "result": 9 + }, + { + "comment": "Escaping a backtick in quotes", + "expression": "`\"foo\\`bar\"`", + "result": "foo`bar" + }, + { + "comment": "Double quote in literal", + "expression": "`\"foo\\\"bar\"`", + "result": "foo\"bar" + }, + { + "expression": "`\"1\\`\"`", + "result": "1`" + }, + { + "comment": "Multiple literal expressions with escapes", + "expression": "`\"\\\\\"`.{a:`\"b\"`}", + "result": {"a": "b"} + }, + { + "comment": "literal . identifier", + "expression": "`{\"a\": \"b\"}`.a", + "result": "b" + }, + { + "comment": "literal . identifier . identifier", + "expression": "`{\"a\": {\"b\": \"c\"}}`.a.b", + "result": "c" + }, + { + "comment": "literal . identifier bracket-expr", + "expression": "`[0, 1, 2]`[1]", + "result": 1 + } + ] + }, + { + "comment": "Literals", + "given": {"type": "object"}, + "cases": [ + { + "comment": "Literal with leading whitespace", + "expression": "` {\"foo\": true}`", + "result": {"foo": true} + }, + { + "comment": "Literal with trailing whitespace", + "expression": "`{\"foo\": true} `", + "result": {"foo": true} + }, + { + "comment": "Literal on RHS of subexpr not allowed", + "expression": "foo.`\"bar\"`", + "error": "syntax" + } + ] + }, + { + "comment": "Raw String Literals", + "given": {}, + "cases": [ + { + "expression": "'foo'", + "result": "foo" + }, + { + "expression": "' foo '", + "result": " foo " + }, + { + "expression": "'0'", + "result": "0" + }, + { + "expression": "'newline\n'", + "result": "newline\n" + }, + { + "expression": "'\n'", + "result": "\n" + }, + { + "expression": "'✓'", + "result": "✓" + }, + { + "expression": "'𝄞'", + "result": "𝄞" + }, + { + "expression": "' [foo] '", + "result": " [foo] " + }, + { + "expression": "'[foo]'", + "result": "[foo]" + }, + { + "comment": "Do not interpret escaped unicode.", + "expression": "'\\u03a6'", + "result": "\\u03a6" + }, + { + "comment": "Can escape the single quote", + "expression": "'foo\\'bar'", + "result": "foo'bar" + }, + { + "comment": "Backslash not followed by single quote is treated as any other character", + "expression": "'\\z'", + "result": "\\z" + }, + { + "comment": "Backslash not followed by single quote is treated as any other character", + "expression": "'\\\\'", + "result": "\\\\" + } + ] + } +] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/multiselect.json b/vendor/mtdowling/jmespath.php/tests/compliance/multiselect.json new file mode 100644 index 0000000..4f46482 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/multiselect.json @@ -0,0 +1,398 @@ +[{ + "given": { + "foo": { + "bar": "bar", + "baz": "baz", + "qux": "qux", + "nested": { + "one": { + "a": "first", + "b": "second", + "c": "third" + }, + "two": { + "a": "first", + "b": "second", + "c": "third" + }, + "three": { + "a": "first", + "b": "second", + "c": {"inner": "third"} + } + } + }, + "bar": 1, + "baz": 2, + "qux\"": 3 + }, + "cases": [ + { + "expression": "foo.{bar: bar}", + "result": {"bar": "bar"} + }, + { + "expression": "foo.{\"bar\": bar}", + "result": {"bar": "bar"} + }, + { + "expression": "foo.{\"foo.bar\": bar}", + "result": {"foo.bar": "bar"} + }, + { + "expression": "foo.{bar: bar, baz: baz}", + "result": {"bar": "bar", "baz": "baz"} + }, + { + "expression": "foo.{\"bar\": bar, \"baz\": baz}", + "result": {"bar": "bar", "baz": "baz"} + }, + { + "expression": "{\"baz\": baz, \"qux\\\"\": \"qux\\\"\"}", + "result": {"baz": 2, "qux\"": 3} + }, + { + "expression": "foo.{bar:bar,baz:baz}", + "result": {"bar": "bar", "baz": "baz"} + }, + { + "expression": "foo.{bar: bar,qux: qux}", + "result": {"bar": "bar", "qux": "qux"} + }, + { + "expression": "foo.{bar: bar, noexist: noexist}", + "result": {"bar": "bar", "noexist": null} + }, + { + "expression": "foo.{noexist: noexist, alsonoexist: alsonoexist}", + "result": {"noexist": null, "alsonoexist": null} + }, + { + "expression": "foo.badkey.{nokey: nokey, alsonokey: alsonokey}", + "result": null + }, + { + "expression": "foo.nested.*.{a: a,b: b}", + "result": [{"a": "first", "b": "second"}, + {"a": "first", "b": "second"}, + {"a": "first", "b": "second"}] + }, + { + "expression": "foo.nested.three.{a: a, cinner: c.inner}", + "result": {"a": "first", "cinner": "third"} + }, + { + "expression": "foo.nested.three.{a: a, c: c.inner.bad.key}", + "result": {"a": "first", "c": null} + }, + { + "expression": "foo.{a: nested.one.a, b: nested.two.b}", + "result": {"a": "first", "b": "second"} + }, + { + "expression": "{bar: bar, baz: baz}", + "result": {"bar": 1, "baz": 2} + }, + { + "expression": "{bar: bar}", + "result": {"bar": 1} + }, + { + "expression": "{otherkey: bar}", + "result": {"otherkey": 1} + }, + { + "expression": "{no: no, exist: exist}", + "result": {"no": null, "exist": null} + }, + { + "expression": "foo.[bar]", + "result": ["bar"] + }, + { + "expression": "foo.[bar,baz]", + "result": ["bar", "baz"] + }, + { + "expression": "foo.[bar,qux]", + "result": ["bar", "qux"] + }, + { + "expression": "foo.[bar,noexist]", + "result": ["bar", null] + }, + { + "expression": "foo.[noexist,alsonoexist]", + "result": [null, null] + } + ] +}, { + "given": { + "foo": {"bar": 1, "baz": [2, 3, 4]} + }, + "cases": [ + { + "expression": "foo.{bar:bar,baz:baz}", + "result": {"bar": 1, "baz": [2, 3, 4]} + }, + { + "expression": "foo.[bar,baz[0]]", + "result": [1, 2] + }, + { + "expression": "foo.[bar,baz[1]]", + "result": [1, 3] + }, + { + "expression": "foo.[bar,baz[2]]", + "result": [1, 4] + }, + { + "expression": "foo.[bar,baz[3]]", + "result": [1, null] + }, + { + "expression": "foo.[bar[0],baz[3]]", + "result": [null, null] + } + ] +}, { + "given": { + "foo": {"bar": 1, "baz": 2} + }, + "cases": [ + { + "expression": "foo.{bar: bar, baz: baz}", + "result": {"bar": 1, "baz": 2} + }, + { + "expression": "foo.[bar,baz]", + "result": [1, 2] + } + ] +}, { + "given": { + "foo": { + "bar": {"baz": [{"common": "first", "one": 1}, + {"common": "second", "two": 2}]}, + "ignoreme": 1, + "includeme": true + } + }, + "cases": [ + { + "expression": "foo.{bar: bar.baz[1],includeme: includeme}", + "result": {"bar": {"common": "second", "two": 2}, "includeme": true} + }, + { + "expression": "foo.{\"bar.baz.two\": bar.baz[1].two, includeme: includeme}", + "result": {"bar.baz.two": 2, "includeme": true} + }, + { + "expression": "foo.[includeme, bar.baz[*].common]", + "result": [true, ["first", "second"]] + }, + { + "expression": "foo.[includeme, bar.baz[*].none]", + "result": [true, []] + }, + { + "expression": "foo.[includeme, bar.baz[].common]", + "result": [true, ["first", "second"]] + } + ] +}, { + "given": { + "reservations": [{ + "instances": [ + {"id": "id1", + "name": "first"}, + {"id": "id2", + "name": "second"} + ]}, { + "instances": [ + {"id": "id3", + "name": "third"}, + {"id": "id4", + "name": "fourth"} + ]} + ]}, + "cases": [ + { + "expression": "reservations[*].instances[*].{id: id, name: name}", + "result": [[{"id": "id1", "name": "first"}, {"id": "id2", "name": "second"}], + [{"id": "id3", "name": "third"}, {"id": "id4", "name": "fourth"}]] + }, + { + "expression": "reservations[].instances[].{id: id, name: name}", + "result": [{"id": "id1", "name": "first"}, + {"id": "id2", "name": "second"}, + {"id": "id3", "name": "third"}, + {"id": "id4", "name": "fourth"}] + }, + { + "expression": "reservations[].instances[].[id, name]", + "result": [["id1", "first"], + ["id2", "second"], + ["id3", "third"], + ["id4", "fourth"]] + } + ] +}, +{ + "given": { + "foo": [{ + "bar": [ + { + "qux": 2, + "baz": 1 + }, + { + "qux": 4, + "baz": 3 + } + ] + }, + { + "bar": [ + { + "qux": 6, + "baz": 5 + }, + { + "qux": 8, + "baz": 7 + } + ] + } + ] + }, + "cases": [ + { + "expression": "foo", + "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, + {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] + }, + { + "expression": "foo[]", + "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, + {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] + }, + { + "expression": "foo[].bar", + "result": [[{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}], + [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]] + }, + { + "expression": "foo[].bar[]", + "result": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}, + {"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}] + }, + { + "expression": "foo[].bar[].[baz, qux]", + "result": [[1, 2], [3, 4], [5, 6], [7, 8]] + }, + { + "expression": "foo[].bar[].[baz]", + "result": [[1], [3], [5], [7]] + }, + { + "expression": "foo[].bar[].[baz, qux][]", + "result": [1, 2, 3, 4, 5, 6, 7, 8] + } + ] +}, +{ + "given": { + "foo": { + "baz": [ + { + "bar": "abc" + }, { + "bar": "def" + } + ], + "qux": ["zero"] + } + }, + "cases": [ + { + "expression": "foo.[baz[*].bar, qux[0]]", + "result": [["abc", "def"], "zero"] + } + ] +}, +{ + "given": { + "foo": { + "baz": [ + { + "bar": "a", + "bam": "b", + "boo": "c" + }, { + "bar": "d", + "bam": "e", + "boo": "f" + } + ], + "qux": ["zero"] + } + }, + "cases": [ + { + "expression": "foo.[baz[*].[bar, boo], qux[0]]", + "result": [[["a", "c" ], ["d", "f" ]], "zero"] + } + ] +}, +{ + "given": { + "foo": { + "baz": [ + { + "bar": "a", + "bam": "b", + "boo": "c" + }, { + "bar": "d", + "bam": "e", + "boo": "f" + } + ], + "qux": ["zero"] + } + }, + "cases": [ + { + "expression": "foo.[baz[*].not_there || baz[*].bar, qux[0]]", + "result": [["a", "d"], "zero"] + } + ] +}, +{ + "given": {"type": "object"}, + "cases": [ + { + "comment": "Nested multiselect", + "expression": "[[*],*]", + "result": [null, ["object"]] + } + ] +}, +{ + "given": [], + "cases": [ + { + "comment": "Nested multiselect", + "expression": "[[*]]", + "result": [[]] + }, + { + "comment": "Select on null", + "expression": "missing.{foo: bar}", + "result": null + } + ] +} +] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/perf/basic.json b/vendor/mtdowling/jmespath.php/tests/compliance/perf/basic.json new file mode 100644 index 0000000..0e70fd1 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/perf/basic.json @@ -0,0 +1,27 @@ +[{ + "description": "Basic minimal case", + "given": + {"foo": {"bar": {"baz": "correct"}}}, + "cases": [ + { + "name": "single_expression", + "expression": "foo", + "result": {"bar": {"baz": "correct"}} + }, + { + "name": "single_dot_expression", + "expression": "foo.bar", + "result": {"baz": "correct"} + }, + { + "name": "double_dot_expression", + "expression": "foo.bar.baz", + "result": "correct" + }, + { + "name": "dot_no_match", + "expression": "foo.bar.baz.bad", + "result": null + } + ] +}] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/perf/deep_hierarchy.json b/vendor/mtdowling/jmespath.php/tests/compliance/perf/deep_hierarchy.json new file mode 100644 index 0000000..aecffe2 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/perf/deep_hierarchy.json @@ -0,0 +1,27 @@ +[{ + "description": "Deeply nested dict", + "given": + {"j49": {"j48": {"j47": {"j46": {"j45": {"j44": {"j43": {"j42": {"j41": {"j40": {"j39": {"j38": {"j37": {"j36": {"j35": {"j34": {"j33": {"j32": {"j31": {"j30": {"j29": {"j28": {"j27": {"j26": {"j25": {"j24": {"j23": {"j22": {"j21": {"j20": {"j19": {"j18": {"j17": {"j16": {"j15": {"j14": {"j13": {"j12": {"j11": {"j10": {"j9": {"j8": {"j7": {"j6": {"j5": {"j4": {"j3": {"j2": {"j1": {"j0": {}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}, + "cases": [ + { + "name": "deep_nesting_10", + "expression": "j49.j48.j47.j46.j45.j44.j43.j42.j41.j40", + "result": {"j39": {"j38": {"j37": {"j36": {"j35": {"j34": {"j33": {"j32": {"j31": {"j30": {"j29": {"j28": {"j27": {"j26": {"j25": {"j24": {"j23": {"j22": {"j21": {"j20": {"j19": {"j18": {"j17": {"j16": {"j15": {"j14": {"j13": {"j12": {"j11": {"j10": {"j9": {"j8": {"j7": {"j6": {"j5": {"j4": {"j3": {"j2": {"j1": {"j0": {}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} + }, + { + "name": "deep_nesting_50", + "expression": "j49.j48.j47.j46.j45.j44.j43.j42.j41.j40.j39.j38.j37.j36.j35.j34.j33.j32.j31.j30.j29.j28.j27.j26.j25.j24.j23.j22.j21.j20.j19.j18.j17.j16.j15.j14.j13.j12.j11.j10.j9.j8.j7.j6.j5.j4.j3.j2.j1.j0", + "result": {} + }, + { + "name": "deep_nesting_50_pipe", + "expression": "j49|j48|j47|j46|j45|j44|j43|j42|j41|j40|j39|j38|j37|j36|j35|j34|j33|j32|j31|j30|j29|j28|j27|j26|j25|j24|j23|j22|j21|j20|j19|j18|j17|j16|j15|j14|j13|j12|j11|j10|j9|j8|j7|j6|j5|j4|j3|j2|j1|j0", + "result": {} + }, + { + "name": "deep_nesting_50_index", + "expression": "[49][48][47][46][45][44][43][42][41][40][39][38][37][36][35][34][33][32][31][30][29][28][27][26][25][24][23][22][21][20][19][18][17][16][15][14][13][12][11][10][9][8][7][6][5][4][3][2][1][0]", + "result": null + } + ] +}] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/perf/deep_projection.json b/vendor/mtdowling/jmespath.php/tests/compliance/perf/deep_projection.json new file mode 100644 index 0000000..fecd6af --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/perf/deep_projection.json @@ -0,0 +1,12 @@ +[{ + "description": "Deep projections", + "given": + {"a": []}, + "cases": [ + { + "name": "deep_projection_104", + "expression": "a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*]", + "result": [] + } + ] +}] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/perf/functions.json b/vendor/mtdowling/jmespath.php/tests/compliance/perf/functions.json new file mode 100644 index 0000000..0122283 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/perf/functions.json @@ -0,0 +1,17 @@ +[{ + "description": "Deep projections", + "given": + [749, 222, 102, 148, 869, 848, 326, 644, 402, 150, 361, 827, 741, 60, 842, 943, 214, 519, 134, 866, 621, 851, 59, 580, 760, 576, 951, 989, 266, 259, 809, 643, 292, 731, 129, 970, 589, 430, 690, 715, 901, 491, 276, 88, 738, 282, 547, 349, 236, 879, 403, 557, 554, 23, 649, 720, 531, 2, 601, 152, 530, 477, 568, 122, 811, 75, 181, 203, 683, 152, 794, 155, 54, 314, 957, 468, 740, 532, 504, 806, 927, 827, 840, 100, 519, 357, 536, 398, 417, 543, 599, 383, 144, 772, 988, 184, 118, 921, 497, 193, 320, 919, 583, 346, 575, 143, 866, 907, 570, 255, 539, 164, 764, 256, 315, 305, 960, 587, 804, 577, 667, 869, 563, 956, 677, 469, 934, 52, 323, 933, 398, 305, 138, 133, 443, 419, 717, 838, 287, 177, 192, 210, 892, 319, 470, 76, 643, 737, 135, 425, 586, 882, 844, 113, 268, 323, 938, 569, 374, 295, 648, 27, 703, 530, 667, 118, 176, 972, 611, 60, 47, 19, 500, 344, 332, 452, 647, 388, 188, 235, 151, 353, 219, 766, 626, 885, 456, 182, 363, 617, 236, 285, 152, 87, 666, 429, 599, 762, 13, 778, 634, 43, 199, 361, 300, 370, 957, 488, 359, 354, 972, 368, 482, 88, 766, 709, 804, 637, 368, 950, 752, 932, 638, 291, 177, 739, 740, 357, 928, 964, 621, 472, 813, 36, 271, 642, 3, 771, 397, 670, 324, 244, 827, 194, 693, 846, 351, 668, 911, 600, 682, 735, 26, 876, 581, 915, 184, 263, 857, 960, 5, 523, 932, 694, 457, 739, 897, 28, 794, 885, 77, 768, 39, 763, 748, 792, 60, 582, 667, 909, 820, 898, 569, 252, 583, 237, 677, 613, 914, 956, 541, 297, 853, 581, 118, 888, 368, 156, 582, 183], + "cases": [ + { + "name": "min sort with slice", + "expression": "sort(@)[:3]", + "result": [2, 3, 5] + }, + { + "name": "max sort with slice", + "expression": "sort(@)[-3:]", + "result": [972, 988, 989] + } + ] +}] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/perf/multiwildcard.json b/vendor/mtdowling/jmespath.php/tests/compliance/perf/multiwildcard.json new file mode 100644 index 0000000..c2a2c0b --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/perf/multiwildcard.json @@ -0,0 +1,22 @@ +[{ + "description": "Multiple wildcards in an expression", + "given": { + "foo": [ + {"bar": [{"kind": "basic"}, {"kind": "intermediate"}]}, + {"bar": [{"kind": "advanced"}, {"kind": "expert"}]} + ] + + }, + "cases": [ + { + "name": "multi_wildcard_field", + "expression": "foo[*].bar[*].kind", + "result": [["basic", "intermediate"], ["advanced", "expert"]] + }, + { + "name": "wildcard_with_index", + "expression": "foo[*].bar[0].kind", + "result": ["basic", "advanced"] + } + ] +}] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/perf/wildcardindex.json b/vendor/mtdowling/jmespath.php/tests/compliance/perf/wildcardindex.json new file mode 100644 index 0000000..57208f9 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/perf/wildcardindex.json @@ -0,0 +1,17 @@ +[{ + "description": "Multiple wildcards", + "given": + {"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]}, + "cases": [ + { + "name": "wildcard_with_field_match", + "expression": "foo[*].bar", + "result": ["one", "two", "three"] + }, + { + "name": "wildcard_with_field_match2", + "expression": "foo[*].notbar", + "result": ["four"] + } + ] +}] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/pipe.json b/vendor/mtdowling/jmespath.php/tests/compliance/pipe.json new file mode 100644 index 0000000..b10c0a4 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/pipe.json @@ -0,0 +1,131 @@ +[{ + "given": { + "foo": { + "bar": { + "baz": "subkey" + }, + "other": { + "baz": "subkey" + }, + "other2": { + "baz": "subkey" + }, + "other3": { + "notbaz": ["a", "b", "c"] + }, + "other4": { + "notbaz": ["a", "b", "c"] + } + } + }, + "cases": [ + { + "expression": "foo.*.baz | [0]", + "result": "subkey" + }, + { + "expression": "foo.*.baz | [1]", + "result": "subkey" + }, + { + "expression": "foo.*.baz | [2]", + "result": "subkey" + }, + { + "expression": "foo.bar.* | [0]", + "result": "subkey" + }, + { + "expression": "foo.*.notbaz | [*]", + "result": [["a", "b", "c"], ["a", "b", "c"]] + }, + { + "expression": "{\"a\": foo.bar, \"b\": foo.other} | *.baz", + "result": ["subkey", "subkey"] + } + ] +}, { + "given": { + "foo": { + "bar": { + "baz": "one" + }, + "other": { + "baz": "two" + }, + "other2": { + "baz": "three" + }, + "other3": { + "notbaz": ["a", "b", "c"] + }, + "other4": { + "notbaz": ["d", "e", "f"] + } + } + }, + "cases": [ + { + "expression": "foo | bar", + "result": {"baz": "one"} + }, + { + "expression": "foo | bar | baz", + "result": "one" + }, + { + "expression": "foo|bar| baz", + "result": "one" + }, + { + "expression": "not_there | [0]", + "result": null + }, + { + "expression": "not_there | [0]", + "result": null + }, + { + "expression": "[foo.bar, foo.other] | [0]", + "result": {"baz": "one"} + }, + { + "expression": "{\"a\": foo.bar, \"b\": foo.other} | a", + "result": {"baz": "one"} + }, + { + "expression": "{\"a\": foo.bar, \"b\": foo.other} | b", + "result": {"baz": "two"} + }, + { + "expression": "foo.bam || foo.bar | baz", + "result": "one" + }, + { + "expression": "foo | not_there || bar", + "result": {"baz": "one"} + } + ] +}, { + "given": { + "foo": [{ + "bar": [{ + "baz": "one" + }, { + "baz": "two" + }] + }, { + "bar": [{ + "baz": "three" + }, { + "baz": "four" + }] + }] + }, + "cases": [ + { + "expression": "foo[*].bar[*] | [0][0]", + "result": {"baz": "one"} + } + ] +}] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/slice.json b/vendor/mtdowling/jmespath.php/tests/compliance/slice.json new file mode 100644 index 0000000..3594772 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/slice.json @@ -0,0 +1,187 @@ +[{ + "given": { + "foo": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "bar": { + "baz": 1 + } + }, + "cases": [ + { + "expression": "bar[0:10]", + "result": null + }, + { + "expression": "foo[0:10:1]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[0:10]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[0:10:]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[0::1]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[0::]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[0:]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[:10:1]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[::1]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[:10:]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[::]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[:]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[1:9]", + "result": [1, 2, 3, 4, 5, 6, 7, 8] + }, + { + "expression": "foo[0:10:2]", + "result": [0, 2, 4, 6, 8] + }, + { + "expression": "foo[5:]", + "result": [5, 6, 7, 8, 9] + }, + { + "expression": "foo[5::2]", + "result": [5, 7, 9] + }, + { + "expression": "foo[::2]", + "result": [0, 2, 4, 6, 8] + }, + { + "expression": "foo[::-1]", + "result": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + }, + { + "expression": "foo[1::2]", + "result": [1, 3, 5, 7, 9] + }, + { + "expression": "foo[10:0:-1]", + "result": [9, 8, 7, 6, 5, 4, 3, 2, 1] + }, + { + "expression": "foo[10:5:-1]", + "result": [9, 8, 7, 6] + }, + { + "expression": "foo[8:2:-2]", + "result": [8, 6, 4] + }, + { + "expression": "foo[0:20]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[10:-20:-1]", + "result": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + }, + { + "expression": "foo[10:-20]", + "result": [] + }, + { + "expression": "foo[-4:-1]", + "result": [6, 7, 8] + }, + { + "expression": "foo[:-5:-1]", + "result": [9, 8, 7, 6] + }, + { + "expression": "foo[8:2:0]", + "error": "invalid-value" + }, + { + "expression": "foo[8:2:0:1]", + "error": "syntax" + }, + { + "expression": "foo[8:2&]", + "error": "syntax" + }, + { + "expression": "foo[2:a:3]", + "error": "syntax" + } + ] +}, { + "given": { + "foo": [{"a": 1}, {"a": 2}, {"a": 3}], + "bar": [{"a": {"b": 1}}, {"a": {"b": 2}}, + {"a": {"b": 3}}], + "baz": 50 + }, + "cases": [ + { + "expression": "foo[:2].a", + "result": [1, 2] + }, + { + "expression": "foo[:2].b", + "result": [] + }, + { + "expression": "foo[:2].a.b", + "result": [] + }, + { + "expression": "bar[::-1].a.b", + "result": [3, 2, 1] + }, + { + "expression": "bar[:2].a.b", + "result": [1, 2] + }, + { + "expression": "baz[:2].a", + "result": null + } + ] +}, { + "given": [{"a": 1}, {"a": 2}, {"a": 3}], + "cases": [ + { + "expression": "[:]", + "result": [{"a": 1}, {"a": 2}, {"a": 3}] + }, + { + "expression": "[:2].a", + "result": [1, 2] + }, + { + "expression": "[::-1].a", + "result": [3, 2, 1] + }, + { + "expression": "[:2].b", + "result": [] + } + ] +}] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/syntax.json b/vendor/mtdowling/jmespath.php/tests/compliance/syntax.json new file mode 100644 index 0000000..a7dc962 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/syntax.json @@ -0,0 +1,692 @@ +[{ + "comment": "Dot syntax", + "given": {"type": "object"}, + "cases": [ + { + "expression": "foo.bar", + "result": null + }, + { + "expression": "foo", + "result": null + }, + { + "expression": "foo.1", + "error": "syntax" + }, + { + "expression": "foo.-11", + "error": "syntax" + }, + { + "expression": "foo.", + "error": "syntax" + }, + { + "expression": ".foo", + "error": "syntax" + }, + { + "expression": "foo..bar", + "error": "syntax" + }, + { + "expression": "foo.bar.", + "error": "syntax" + }, + { + "expression": "foo[.]", + "error": "syntax" + } + ] +}, + { + "comment": "Simple token errors", + "given": {"type": "object"}, + "cases": [ + { + "expression": ".", + "error": "syntax" + }, + { + "expression": ":", + "error": "syntax" + }, + { + "expression": ",", + "error": "syntax" + }, + { + "expression": "]", + "error": "syntax" + }, + { + "expression": "[", + "error": "syntax" + }, + { + "expression": "}", + "error": "syntax" + }, + { + "expression": "{", + "error": "syntax" + }, + { + "expression": ")", + "error": "syntax" + }, + { + "expression": "(", + "error": "syntax" + }, + { + "expression": "((&", + "error": "syntax" + }, + { + "expression": "a[", + "error": "syntax" + }, + { + "expression": "a]", + "error": "syntax" + }, + { + "expression": "a][", + "error": "syntax" + }, + { + "expression": "!", + "error": "syntax" + }, + { + "expression": "@=", + "error": "syntax" + }, + { + "expression": "@``", + "error": "syntax" + } + ] + }, + { + "comment": "Boolean syntax errors", + "given": {"type": "object"}, + "cases": [ + { + "expression": "![!(!", + "error": "syntax" + } + ] + }, + { + "comment": "Paren syntax errors", + "given": {}, + "cases": [ + { + "comment": "missing closing paren", + "expression": "(@", + "error": "syntax" + } + ] + }, + { + "comment": "Function syntax errors", + "given": {}, + "cases": [ + { + "comment": "invalid start of function", + "expression": "@(foo)", + "error": "syntax" + }, + { + "comment": "function names cannot be quoted", + "expression": "\"foo\"(bar)", + "error": "syntax" + } + ] + }, + { + "comment": "Wildcard syntax", + "given": {"type": "object"}, + "cases": [ + { + "expression": "*", + "result": ["object"] + }, + { + "expression": "*.*", + "result": [] + }, + { + "expression": "*.foo", + "result": [] + }, + { + "expression": "*[0]", + "result": [] + }, + { + "expression": ".*", + "error": "syntax" + }, + { + "expression": "*foo", + "error": "syntax" + }, + { + "expression": "*0", + "error": "syntax" + }, + { + "expression": "foo[*]bar", + "error": "syntax" + }, + { + "expression": "foo[*]*", + "error": "syntax" + } + ] + }, + { + "comment": "Flatten syntax", + "given": {"type": "object"}, + "cases": [ + { + "expression": "[]", + "result": null + } + ] + }, + { + "comment": "Simple bracket syntax", + "given": {"type": "object"}, + "cases": [ + { + "expression": "[0]", + "result": null + }, + { + "expression": "[*]", + "result": null + }, + { + "expression": "*.[0]", + "error": "syntax" + }, + { + "expression": "*.[\"0\"]", + "result": [[null]] + }, + { + "expression": "[*].bar", + "result": null + }, + { + "expression": "[*][0]", + "result": null + }, + { + "expression": "foo[#]", + "error": "syntax" + }, + { + "comment": "missing rbracket for led wildcard index", + "expression": "led[*", + "error": "syntax" + } + ] + }, + { + "comment": "slice syntax", + "given": {}, + "cases": [ + { + "comment": "slice expected colon or rbracket", + "expression": "[:@]", + "error": "syntax" + }, + { + "comment": "slice has too many colons", + "expression": "[:::]", + "error": "syntax" + }, + { + "comment": "slice expected number", + "expression": "[:@:]", + "error": "syntax" + }, + { + "comment": "slice expected number of colon", + "expression": "[:1@]", + "error": "syntax" + } + ] + }, + { + "comment": "Multi-select list syntax", + "given": {"type": "object"}, + "cases": [ + { + "expression": "foo[0]", + "result": null + }, + { + "comment": "Valid multi-select of a list", + "expression": "foo[0, 1]", + "error": "syntax" + }, + { + "expression": "foo.[0]", + "error": "syntax" + }, + { + "expression": "foo.[*]", + "result": null + }, + { + "comment": "Multi-select of a list with trailing comma", + "expression": "foo[0, ]", + "error": "syntax" + }, + { + "comment": "Multi-select of a list with trailing comma and no close", + "expression": "foo[0,", + "error": "syntax" + }, + { + "comment": "Multi-select of a list with trailing comma and no close", + "expression": "foo.[a", + "error": "syntax" + }, + { + "comment": "Multi-select of a list with extra comma", + "expression": "foo[0,, 1]", + "error": "syntax" + }, + { + "comment": "Multi-select of a list using an identifier index", + "expression": "foo[abc]", + "error": "syntax" + }, + { + "comment": "Multi-select of a list using identifier indices", + "expression": "foo[abc, def]", + "error": "syntax" + }, + { + "comment": "Multi-select of a list using an identifier index", + "expression": "foo[abc, 1]", + "error": "syntax" + }, + { + "comment": "Multi-select of a list using an identifier index with trailing comma", + "expression": "foo[abc, ]", + "error": "syntax" + }, + { + "comment": "Valid multi-select of a hash using an identifier index", + "expression": "foo.[abc]", + "result": null + }, + { + "comment": "Valid multi-select of a hash", + "expression": "foo.[abc, def]", + "result": null + }, + { + "comment": "Multi-select of a hash using a numeric index", + "expression": "foo.[abc, 1]", + "error": "syntax" + }, + { + "comment": "Multi-select of a hash with a trailing comma", + "expression": "foo.[abc, ]", + "error": "syntax" + }, + { + "comment": "Multi-select of a hash with extra commas", + "expression": "foo.[abc,, def]", + "error": "syntax" + }, + { + "comment": "Multi-select of a hash using number indices", + "expression": "foo.[0, 1]", + "error": "syntax" + } + ] + }, + { + "comment": "Multi-select hash syntax", + "given": {"type": "object"}, + "cases": [ + { + "comment": "No key or value", + "expression": "a{}", + "error": "syntax" + }, + { + "comment": "No closing token", + "expression": "a{", + "error": "syntax" + }, + { + "comment": "Not a key value pair", + "expression": "a{foo}", + "error": "syntax" + }, + { + "comment": "Missing value and closing character", + "expression": "a{foo:", + "error": "syntax" + }, + { + "comment": "Missing closing character", + "expression": "a{foo: 0", + "error": "syntax" + }, + { + "comment": "Missing value", + "expression": "a{foo:}", + "error": "syntax" + }, + { + "comment": "Trailing comma and no closing character", + "expression": "a{foo: 0, ", + "error": "syntax" + }, + { + "comment": "Missing value with trailing comma", + "expression": "a{foo: ,}", + "error": "syntax" + }, + { + "comment": "Accessing Array using an identifier", + "expression": "a{foo: bar}", + "error": "syntax" + }, + { + "expression": "a{foo: 0}", + "error": "syntax" + }, + { + "comment": "Missing key-value pair", + "expression": "a.{}", + "error": "syntax" + }, + { + "comment": "Not a key-value pair", + "expression": "a.{foo}", + "error": "syntax" + }, + { + "comment": "Missing value", + "expression": "a.{foo:}", + "error": "syntax" + }, + { + "comment": "Missing value with trailing comma", + "expression": "a.{foo: ,}", + "error": "syntax" + }, + { + "comment": "Valid multi-select hash extraction", + "expression": "a.{foo: bar}", + "result": null + }, + { + "comment": "Valid multi-select hash extraction", + "expression": "a.{foo: bar, baz: bam}", + "result": null + }, + { + "comment": "Trailing comma", + "expression": "a.{foo: bar, }", + "error": "syntax" + }, + { + "comment": "Missing key in second key-value pair", + "expression": "a.{foo: bar, baz}", + "error": "syntax" + }, + { + "comment": "Missing value in second key-value pair", + "expression": "a.{foo: bar, baz:}", + "error": "syntax" + }, + { + "comment": "Trailing comma", + "expression": "a.{foo: bar, baz: bam, }", + "error": "syntax" + }, + { + "comment": "Nested multi select", + "expression": "{\"\\\\\":{\" \":*}}", + "result": {"\\": {" ": ["object"]}} + }, + { + "comment": "Missing closing } after a valid nud", + "expression": "{a: @", + "error": "syntax" + } + ] + }, + { + "comment": "Or expressions", + "given": {"type": "object"}, + "cases": [ + { + "expression": "foo || bar", + "result": null + }, + { + "expression": "foo ||", + "error": "syntax" + }, + { + "expression": "foo.|| bar", + "error": "syntax" + }, + { + "expression": " || foo", + "error": "syntax" + }, + { + "expression": "foo || || foo", + "error": "syntax" + }, + { + "expression": "foo.[a || b]", + "result": null + }, + { + "expression": "foo.[a ||]", + "error": "syntax" + }, + { + "expression": "\"foo", + "error": "syntax" + } + ] + }, + { + "comment": "Filter expressions", + "given": {"type": "object"}, + "cases": [ + { + "expression": "foo[?bar==`\"baz\"`]", + "result": null + }, + { + "expression": "foo[? bar == `\"baz\"` ]", + "result": null + }, + { + "expression": "foo[ ?bar==`\"baz\"`]", + "error": "syntax" + }, + { + "expression": "foo[?bar==]", + "error": "syntax" + }, + { + "expression": "foo[?==]", + "error": "syntax" + }, + { + "expression": "foo[?==bar]", + "error": "syntax" + }, + { + "expression": "foo[?bar==baz?]", + "error": "syntax" + }, + { + "expression": "foo[?a.b.c==d.e.f]", + "result": null + }, + { + "expression": "foo[?bar==`[0, 1, 2]`]", + "result": null + }, + { + "expression": "foo[?bar==`[\"a\", \"b\", \"c\"]`]", + "result": null + }, + { + "comment": "Literal char not escaped", + "expression": "foo[?bar==`[\"foo`bar\"]`]", + "error": "syntax" + }, + { + "comment": "Literal char escaped", + "expression": "foo[?bar==`[\"foo\\`bar\"]`]", + "result": null + }, + { + "comment": "Unknown comparator", + "expression": "foo[?bar<>baz]", + "error": "syntax" + }, + { + "comment": "Unknown comparator", + "expression": "foo[?bar^baz]", + "error": "syntax" + }, + { + "expression": "foo[bar==baz]", + "error": "syntax" + }, + { + "comment": "Quoted identifier in filter expression no spaces", + "expression": "[?\"\\\\\">`\"foo\"`]", + "result": null + }, + { + "comment": "Quoted identifier in filter expression with spaces", + "expression": "[?\"\\\\\" > `\"foo\"`]", + "result": null + } + ] + }, + { + "comment": "Filter expression errors", + "given": {"type": "object"}, + "cases": [ + { + "expression": "bar.`\"anything\"`", + "error": "syntax" + }, + { + "expression": "bar.baz.noexists.`\"literal\"`", + "error": "syntax" + }, + { + "comment": "Literal wildcard projection", + "expression": "foo[*].`\"literal\"`", + "error": "syntax" + }, + { + "expression": "foo[*].name.`\"literal\"`", + "error": "syntax" + }, + { + "expression": "foo[].name.`\"literal\"`", + "error": "syntax" + }, + { + "expression": "foo[].name.`\"literal\"`.`\"subliteral\"`", + "error": "syntax" + }, + { + "comment": "Projecting a literal onto an empty list", + "expression": "foo[*].name.noexist.`\"literal\"`", + "error": "syntax" + }, + { + "expression": "foo[].name.noexist.`\"literal\"`", + "error": "syntax" + }, + { + "expression": "twolen[*].`\"foo\"`", + "error": "syntax" + }, + { + "comment": "Two level projection of a literal", + "expression": "twolen[*].threelen[*].`\"bar\"`", + "error": "syntax" + }, + { + "comment": "Two level flattened projection of a literal", + "expression": "twolen[].threelen[].`\"bar\"`", + "error": "syntax" + }, + { + "comment": "expects closing ]", + "expression": "foo[? @ | @", + "error": "syntax" + } + ] + }, + { + "comment": "Identifiers", + "given": {"type": "object"}, + "cases": [ + { + "expression": "foo", + "result": null + }, + { + "expression": "\"foo\"", + "result": null + }, + { + "expression": "\"\\\\\"", + "result": null + }, + { + "expression": "\"\\u\"", + "error": "syntax" + } + ] + }, + { + "comment": "Combined syntax", + "given": [], + "cases": [ + { + "expression": "*||*|*|*", + "result": [] + }, + { + "expression": "*[]||[*]", + "result": [] + }, + { + "expression": "[*.*]", + "result": [[]] + } + ] + } +] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/unicode.json b/vendor/mtdowling/jmespath.php/tests/compliance/unicode.json new file mode 100644 index 0000000..6b07b0b --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/unicode.json @@ -0,0 +1,38 @@ +[ + { + "given": {"foo": [{"✓": "✓"}, {"✓": "✗"}]}, + "cases": [ + { + "expression": "foo[].\"✓\"", + "result": ["✓", "✗"] + } + ] + }, + { + "given": {"☯": true}, + "cases": [ + { + "expression": "\"☯\"", + "result": true + } + ] + }, + { + "given": {"♪♫•*¨*•.¸¸❤¸¸.•*¨*•♫♪": true}, + "cases": [ + { + "expression": "\"♪♫•*¨*•.¸¸❤¸¸.•*¨*•♫♪\"", + "result": true + } + ] + }, + { + "given": {"☃": true}, + "cases": [ + { + "expression": "\"☃\"", + "result": true + } + ] + } +] diff --git a/vendor/mtdowling/jmespath.php/tests/compliance/wildcard.json b/vendor/mtdowling/jmespath.php/tests/compliance/wildcard.json new file mode 100644 index 0000000..3bcec30 --- /dev/null +++ b/vendor/mtdowling/jmespath.php/tests/compliance/wildcard.json @@ -0,0 +1,460 @@ +[{ + "given": { + "foo": { + "bar": { + "baz": "val" + }, + "other": { + "baz": "val" + }, + "other2": { + "baz": "val" + }, + "other3": { + "notbaz": ["a", "b", "c"] + }, + "other4": { + "notbaz": ["a", "b", "c"] + }, + "other5": { + "other": { + "a": 1, + "b": 1, + "c": 1 + } + } + } + }, + "cases": [ + { + "expression": "foo.*.baz", + "result": ["val", "val", "val"] + }, + { + "expression": "foo.bar.*", + "result": ["val"] + }, + { + "expression": "foo.*.notbaz", + "result": [["a", "b", "c"], ["a", "b", "c"]] + }, + { + "expression": "foo.*.notbaz[0]", + "result": ["a", "a"] + }, + { + "expression": "foo.*.notbaz[-1]", + "result": ["c", "c"] + } + ] +}, { + "given": { + "foo": { + "first-1": { + "second-1": "val" + }, + "first-2": { + "second-1": "val" + }, + "first-3": { + "second-1": "val" + } + } + }, + "cases": [ + { + "expression": "foo.*", + "result": [{"second-1": "val"}, {"second-1": "val"}, + {"second-1": "val"}] + }, + { + "expression": "foo.*.*", + "result": [["val"], ["val"], ["val"]] + }, + { + "expression": "foo.*.*.*", + "result": [[], [], []] + }, + { + "expression": "foo.*.*.*.*", + "result": [[], [], []] + } + ] +}, { + "given": { + "foo": { + "bar": "one" + }, + "other": { + "bar": "one" + }, + "nomatch": { + "notbar": "three" + } + }, + "cases": [ + { + "expression": "*.bar", + "result": ["one", "one"] + } + ] +}, { + "given": { + "top1": { + "sub1": {"foo": "one"} + }, + "top2": { + "sub1": {"foo": "one"} + } + }, + "cases": [ + { + "expression": "*", + "result": [{"sub1": {"foo": "one"}}, + {"sub1": {"foo": "one"}}] + }, + { + "expression": "*.sub1", + "result": [{"foo": "one"}, + {"foo": "one"}] + }, + { + "expression": "*.*", + "result": [[{"foo": "one"}], + [{"foo": "one"}]] + }, + { + "expression": "*.*.foo[]", + "result": ["one", "one"] + }, + { + "expression": "*.sub1.foo", + "result": ["one", "one"] + } + ] +}, +{ + "given": + {"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]}, + "cases": [ + { + "expression": "foo[*].bar", + "result": ["one", "two", "three"] + }, + { + "expression": "foo[*].notbar", + "result": ["four"] + } + ] +}, +{ + "given": + [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}], + "cases": [ + { + "expression": "[*]", + "result": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}] + }, + { + "expression": "[*].bar", + "result": ["one", "two", "three"] + }, + { + "expression": "[*].notbar", + "result": ["four"] + } + ] +}, +{ + "given": { + "foo": { + "bar": [ + {"baz": ["one", "two", "three"]}, + {"baz": ["four", "five", "six"]}, + {"baz": ["seven", "eight", "nine"]} + ] + } + }, + "cases": [ + { + "expression": "foo.bar[*].baz", + "result": [["one", "two", "three"], ["four", "five", "six"], ["seven", "eight", "nine"]] + }, + { + "expression": "foo.bar[*].baz[0]", + "result": ["one", "four", "seven"] + }, + { + "expression": "foo.bar[*].baz[1]", + "result": ["two", "five", "eight"] + }, + { + "expression": "foo.bar[*].baz[2]", + "result": ["three", "six", "nine"] + }, + { + "expression": "foo.bar[*].baz[3]", + "result": [] + } + ] +}, +{ + "given": { + "foo": { + "bar": [["one", "two"], ["three", "four"]] + } + }, + "cases": [ + { + "expression": "foo.bar[*]", + "result": [["one", "two"], ["three", "four"]] + }, + { + "expression": "foo.bar[0]", + "result": ["one", "two"] + }, + { + "expression": "foo.bar[0][0]", + "result": "one" + }, + { + "expression": "foo.bar[0][0][0]", + "result": null + }, + { + "expression": "foo.bar[0][0][0][0]", + "result": null + }, + { + "expression": "foo[0][0]", + "result": null + } + ] +}, +{ + "given": { + "foo": [ + {"bar": [{"kind": "basic"}, {"kind": "intermediate"}]}, + {"bar": [{"kind": "advanced"}, {"kind": "expert"}]}, + {"bar": "string"} + ] + + }, + "cases": [ + { + "expression": "foo[*].bar[*].kind", + "result": [["basic", "intermediate"], ["advanced", "expert"]] + }, + { + "expression": "foo[*].bar[0].kind", + "result": ["basic", "advanced"] + } + ] +}, +{ + "given": { + "foo": [ + {"bar": {"kind": "basic"}}, + {"bar": {"kind": "intermediate"}}, + {"bar": {"kind": "advanced"}}, + {"bar": {"kind": "expert"}}, + {"bar": "string"} + ] + }, + "cases": [ + { + "expression": "foo[*].bar.kind", + "result": ["basic", "intermediate", "advanced", "expert"] + } + ] +}, +{ + "given": { + "foo": [{"bar": ["one", "two"]}, {"bar": ["three", "four"]}, {"bar": ["five"]}] + }, + "cases": [ + { + "expression": "foo[*].bar[0]", + "result": ["one", "three", "five"] + }, + { + "expression": "foo[*].bar[1]", + "result": ["two", "four"] + }, + { + "expression": "foo[*].bar[2]", + "result": [] + } + ] +}, +{ + "given": { + "foo": [{"bar": []}, {"bar": []}, {"bar": []}] + }, + "cases": [ + { + "expression": "foo[*].bar[0]", + "result": [] + } + ] +}, +{ + "given": { + "foo": [["one", "two"], ["three", "four"], ["five"]] + }, + "cases": [ + { + "expression": "foo[*][0]", + "result": ["one", "three", "five"] + }, + { + "expression": "foo[*][1]", + "result": ["two", "four"] + } + ] +}, +{ + "given": { + "foo": [ + [ + ["one", "two"], ["three", "four"] + ], [ + ["five", "six"], ["seven", "eight"] + ], [ + ["nine"], ["ten"] + ] + ] + }, + "cases": [ + { + "expression": "foo[*][0]", + "result": [["one", "two"], ["five", "six"], ["nine"]] + }, + { + "expression": "foo[*][1]", + "result": [["three", "four"], ["seven", "eight"], ["ten"]] + }, + { + "expression": "foo[*][0][0]", + "result": ["one", "five", "nine"] + }, + { + "expression": "foo[*][1][0]", + "result": ["three", "seven", "ten"] + }, + { + "expression": "foo[*][0][1]", + "result": ["two", "six"] + }, + { + "expression": "foo[*][1][1]", + "result": ["four", "eight"] + }, + { + "expression": "foo[*][2]", + "result": [] + }, + { + "expression": "foo[*][2][2]", + "result": [] + }, + { + "expression": "bar[*]", + "result": null + }, + { + "expression": "bar[*].baz[*]", + "result": null + } + ] +}, +{ + "given": { + "string": "string", + "hash": {"foo": "bar", "bar": "baz"}, + "number": 23, + "nullvalue": null + }, + "cases": [ + { + "expression": "string[*]", + "result": null + }, + { + "expression": "hash[*]", + "result": null + }, + { + "expression": "number[*]", + "result": null + }, + { + "expression": "nullvalue[*]", + "result": null + }, + { + "expression": "string[*].foo", + "result": null + }, + { + "expression": "hash[*].foo", + "result": null + }, + { + "expression": "number[*].foo", + "result": null + }, + { + "expression": "nullvalue[*].foo", + "result": null + }, + { + "expression": "nullvalue[*].foo[*].bar", + "result": null + } + ] +}, +{ + "given": { + "string": "string", + "hash": {"foo": "val", "bar": "val"}, + "number": 23, + "array": [1, 2, 3], + "nullvalue": null + }, + "cases": [ + { + "expression": "string.*", + "result": null + }, + { + "expression": "hash.*", + "result": ["val", "val"] + }, + { + "expression": "number.*", + "result": null + }, + { + "expression": "array.*", + "result": null + }, + { + "expression": "nullvalue.*", + "result": null + } + ] +}, +{ + "given": { + "a": [0, 1, 2], + "b": [0, 1, 2] + }, + "cases": [ + { + "expression": "*[0]", + "result": [0, 0] + } + ] +} +] diff --git a/vendor/nette/php-generator/composer.json b/vendor/nette/php-generator/composer.json new file mode 100644 index 0000000..87f424b --- /dev/null +++ b/vendor/nette/php-generator/composer.json @@ -0,0 +1,34 @@ +{ + "name": "nette/php-generator", + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.3 features.", + "keywords": ["nette", "php", "code", "scaffolding"], + "homepage": "https://nette.org", + "license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "require": { + "php": ">=7.1", + "nette/utils": "^2.4.2 || ~3.0.0" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "autoload": { + "classmap": ["src/"] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/nette/php-generator/contributing.md b/vendor/nette/php-generator/contributing.md new file mode 100644 index 0000000..184152c --- /dev/null +++ b/vendor/nette/php-generator/contributing.md @@ -0,0 +1,33 @@ +How to contribute & use the issue tracker +========================================= + +Nette welcomes your contributions. There are several ways to help out: + +* Create an issue on GitHub, if you have found a bug +* Write test cases for open bug issues +* Write fixes for open bug/feature issues, preferably with test cases included +* Contribute to the [documentation](https://nette.org/en/writing) + +Issues +------ + +Please **do not use the issue tracker to ask questions**. We will be happy to help you +on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette). + +A good bug report shouldn't leave others needing to chase you up for more +information. Please try to be as detailed as possible in your report. + +**Feature requests** are welcome. But take a moment to find out whether your idea +fits with the scope and aims of the project. It's up to *you* to make a strong +case to convince the project's developers of the merits of this feature. + +Contributing +------------ + +If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing). + +The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them. + +Please do not fix whitespace, format code, or make a purely cosmetic patch. + +Thanks! :heart: diff --git a/vendor/nette/php-generator/license.md b/vendor/nette/php-generator/license.md new file mode 100644 index 0000000..cf741bd --- /dev/null +++ b/vendor/nette/php-generator/license.md @@ -0,0 +1,60 @@ +Licenses +======== + +Good news! You may use Nette Framework under the terms of either +the New BSD License or the GNU General Public License (GPL) version 2 or 3. + +The BSD License is recommended for most projects. It is easy to understand and it +places almost no restrictions on what you can do with the framework. If the GPL +fits better to your project, you can use the framework under this license. + +You don't have to notify anyone which license you are using. You can freely +use Nette Framework in commercial projects as long as the copyright header +remains intact. + +Please be advised that the name "Nette Framework" is a protected trademark and its +usage has some limitations. So please do not use word "Nette" in the name of your +project or top-level domain, and choose a name that stands on its own merits. +If your stuff is good, it will not take long to establish a reputation for yourselves. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +This software is provided by the copyright holders and contributors "as is" and +any express or implied warranties, including, but not limited to, the implied +warranties of merchantability and fitness for a particular purpose are +disclaimed. In no event shall the copyright owner or contributors be liable for +any direct, indirect, incidental, special, exemplary, or consequential damages +(including, but not limited to, procurement of substitute goods or services; +loss of use, data, or profits; or business interruption) however caused and on +any theory of liability, whether in contract, strict liability, or tort +(including negligence or otherwise) arising in any way out of the use of this +software, even if advised of the possibility of such damage. + + +GNU General Public License +-------------------------- + +GPL licenses are very very long, so instead of including them here we offer +you URLs with full text: + +- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html) +- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html) diff --git a/vendor/nette/php-generator/readme.md b/vendor/nette/php-generator/readme.md new file mode 100644 index 0000000..bd2d5d1 --- /dev/null +++ b/vendor/nette/php-generator/readme.md @@ -0,0 +1,566 @@ +Nette PHP Generator +=================== + +[![Downloads this Month](https://img.shields.io/packagist/dm/nette/php-generator.svg)](https://packagist.org/packages/nette/php-generator) +[![Build Status](https://travis-ci.org/nette/php-generator.svg?branch=master)](https://travis-ci.org/nette/php-generator) +[![Coverage Status](https://coveralls.io/repos/github/nette/php-generator/badge.svg?branch=master&v=1)](https://coveralls.io/github/nette/php-generator?branch=master) +[![Latest Stable Version](https://poser.pugx.org/nette/php-generator/v/stable)](https://github.com/nette/php-generator/releases) +[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/php-generator/blob/master/license.md) + + +Introduction +------------ + +Generate PHP code, classes, namespaces etc. with a simple programmatical API. + +Documentation can be found on the [website](https://doc.nette.org/php-generator). + +If you like Nette, **[please make a donation now](https://nette.org/donate)**. Thank you! + + +Installation +------------ + +The recommended way to install is via Composer: + +``` +composer require nette/php-generator +``` + +- PhpGenerator 3.3 & 3.2 is compatible with PHP 7.1 to 7.4 +- PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3 +- PhpGenerator 3.0 is compatible with PHP 7.0 to 7.3 +- PhpGenerator 2.6 is compatible with PHP 5.6 to 7.3 + + +Usage +----- + +Usage is very easy. Let's start with a straightforward example of generating class: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class + ->setFinal() + ->setExtends('ParentClass') + ->addImplement('Countable') + ->addTrait('Nette\SmartObject') + ->addComment("Description of class.\nSecond line\n") + ->addComment('@property-read Nette\Forms\Form $form'); + +// to generate PHP code simply cast to string or use echo: +echo $class; + +// or use printer: +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); +``` + +It will render this result: + +```php +/** + * Description of class. + * Second line + * + * @property-read Nette\Forms\Form $form + */ +final class Demo extends ParentClass implements Countable +{ + use Nette\SmartObject; +} +``` + +We can add constants and properties: + +```php +$class->addConstant('ID', 123); + +$class->addProperty('items', [1, 2, 3]) + ->setVisibility('private') + ->setStatic() + ->addComment('@var int[]'); +``` + +It generates: + +```php +const ID = 123; + +/** @var int[] */ +private static $items = [1, 2, 3]; +``` + +And we can add methods with parameters: + +```php +$method = $class->addMethod('count') + ->addComment('Count it.') + ->addComment('@return int') + ->setFinal() + ->setVisibility('protected') + ->setBody('return count($items ?: $this->items);'); + +$method->addParameter('items', []) // $items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] +``` + +It results in: + +```php +/** + * Count it. + * @return int + */ +final protected function count(array &$items = []) +{ + return count($items ?: $this->items); +} +``` + +If the property, constant, method or parameter already exist, it will be overwritten. + +Members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()` or `removeParameter()`. + +PHP Generator supports all new PHP 7.3 and 7.4 features: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addConstant('ID', 123) + ->setVisibility('private'); // constant visiblity + +$class->addProperty('items') + ->setType('array') // typed properites + ->setNullable() + ->setInitialized(); + +$method = $class->addMethod('getValue') + ->setReturnType('int') // method return type + ->setReturnNullable() // nullable return type + ->setBody('return count($this->items);'); + +$method->addParameter('id') + ->setType('int') // scalar type hint + ->setNullable(); // nullable type hint + +echo $class; +``` + +Result: + +```php +class Demo +{ + private const ID = 123; + + public ?array $items = null; + + public function getValue(?int $id): ?int + { + return count($this->items); + } +} +``` + +You can also add existing `Method`, `Property` or `Constant` objects to the class: + +```php +$method = new Nette\PhpGenerator\Method('getHandle'); +$property = new Nette\PhpGenerator\Property('handle'); +$const = new Nette\PhpGenerator\Constant('ROLE'); + +$class = (new Nette\PhpGenerator\ClassType('Demo')) + ->addMember($method) + ->addMember($property) + ->addMember($const); +``` + +You can clone existing methods, properties and constants with a different name using `cloneWithName()`: + +```php +$methodCount = $class->getMethod('count'); +$methodRecount = $methodCount->cloneWithName('recount'); +$class->addMember($methodRecount); +``` + +Tabs versus spaces +------------------ + +The generated code uses tabs for indentation. If you want to have the output compatible with PSR-2 or PSR-12, use `PsrPrinter`: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +// ... + +$printer = new Nette\PhpGenerator\PsrPrinter; +echo $printer->printClass($class); // 4 spaces indentation +``` + +It can be used also for functions, closures, namespaces etc. + + +Literals +-------- + +You can pass any PHP code to property or parameter default values via `PhpLiteral`: + +```php +use Nette\PhpGenerator\PhpLiteral; + +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('foo', new PhpLiteral('Iterator::SELF_FIRST')); + +$class->addMethod('bar') + ->addParameter('id', new PhpLiteral('1 + 2')); + +echo $class; +``` + +Result: + +```php +class Demo +{ + public $foo = Iterator::SELF_FIRST; + + public function bar($id = 1 + 2) + { + } +} +``` + +Interface or Trait +------------------ + +```php +$class = new Nette\PhpGenerator\ClassType('DemoInterface'); +$class->setType('interface'); +// or $class->setType('trait'); +``` + +Trait Resolutions and Visibility +-------------------------------- + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$class->addTrait('SmartObject', ['sayHello as protected']); +echo $class; +``` + +Result: + +```php +class Demo +{ + use SmartObject { + sayHello as protected; + } +} +``` + +Anonymous Class +--------------- + +```php +$class = new Nette\PhpGenerator\ClassType(null); +$class->addMethod('__construct') + ->addParameter('foo'); + +echo '$obj = new class ($val) ' . $class . ';'; +``` + +Result: + +```php +$obj = new class ($val) { + + public function __construct($foo) + { + } +}; +``` + +Global Function +--------------- + +Code of function: + +```php +$function = new Nette\PhpGenerator\GlobalFunction('foo'); +$function->setBody('return $a + $b;'); +$function->addParameter('a'); +$function->addParameter('b'); +echo $function; + +// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); +``` + +Result: + +```php +function foo($a, $b) +{ + return $a + $b; +} +``` + +Closure +------- + +Code of closure: + +```php +$closure = new Nette\PhpGenerator\Closure; +$closure->setBody('return $a + $b;'); +$closure->addParameter('a'); +$closure->addParameter('b'); +$closure->addUse('c') + ->setReference(); +echo $closure; + +// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); +``` + +Result: + +```php +function ($a, $b) use (&$c) { + return $a + $b; +} +``` + +Arrow function +-------------- + +You can also print closure as arrow function using printer: + +```php +$closure = new Nette\PhpGenerator\Closure; +$closure->setBody('return $a + $b;'); +$closure->addParameter('a'); +$closure->addParameter('b'); + +// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure); +``` + +Result: + +```php +fn ($a, $b) => $a + $b; +``` + +Method and Function Body Generator +---------------------------------- + +You can use special placeholders for handy way to generate method or function body. + +Simple placeholders: + +```php +$str = 'any string'; +$num = 3; +$function = new Nette\PhpGenerator\GlobalFunction('foo'); +$function->addBody('return strlen(?, ?);', [$str, $num]); +echo $function; +``` + +Result: + +```php +function foo() +{ + return strlen('any string', 3); +} +``` + +Variadic placeholder: + +```php +$items = [1, 2, 3]; +$function = new Nette\PhpGenerator\GlobalFunction('foo'); +$function->setBody('myfunc(...?);', [$items]); +echo $function; +``` + +Result: + +```php +function foo() +{ + myfunc(1, 2, 3); +} +``` + +Escape placeholder using slash: + +```php +$num = 3; +$function = new Nette\PhpGenerator\GlobalFunction('foo'); +$function->addParameter('a'); +$function->addBody('return $a \? 10 : ?;', [$num]); +echo $function; +``` + +Result: + +```php +function foo($a) +{ + return $a ? 10 : 3; +} +``` + +Namespace +--------- + +Classes, traits and interfaces (hereinafter classes) can be grouped into namespaces: + +```php +$namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); + +$class = $namespace->addClass('Task'); +$interface = $namespace->addInterface('Countable'); +$trait = $namespace->addTrait('NameAware'); + +// or +$class = new Nette\PhpGenerator\ClassType('Task'); +$namespace->add($class); +``` + +If the class already exists, it will be overwritten. + +You can define use-statements: + +```php +$namespace->addUse('Http\Request'); // use Http\Request; +$namespace->addUse('Http\Request', 'HttpReq'); // use Http\Request as HttpReq; +``` + +**IMPORTANT NOTE:** when the class is part of the namespace, it is rendered slightly differently: all types (ie. type hints, return types, parent class name, +implemented interfaces and used traits) are automatically *resolved* (unless you turn it off, see below). +It means that you have to **use full class names** in definitions and they will be replaced +with aliases (according to the use-statements) or fully qualified names in the resulting code: + +```php +$namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); +$namespace->addUse('Bar\AliasedClass'); + +$class = $namespace->addClass('Demo'); +$class->addImplement('Foo\A') // it will resolve to A + ->addTrait('Bar\AliasedClass'); // it will resolve to AliasedClass + +$method = $class->addMethod('method'); +$method->addComment('@return ' . $namespace->unresolveName('Foo\D')); // in comments resolve manually +$method->addParameter('arg') + ->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass + +echo $namespace; + +// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); +``` + +Result: + +```php +namespace Foo; + +use Bar\AliasedClass; + +class Demo implements A +{ + use AliasedClass; + + /** + * @return D + */ + public function method(\Bar\OtherClass $arg) + { + } +} +``` + +Auto-resolving can be turned off this way: + +```php +$printer = new Nette\PhpGenerator\Printer; // or PsrPrinter +$printer->setTypeResolving(false); +echo $printer->printNamespace($namespace); +``` + +PHP Files +--------- + +PHP files can contains multiple classes, namespaces and comments: + +```php +$file = new Nette\PhpGenerator\PhpFile; +$file->addComment('This file is auto-generated.'); +$file->setStrictTypes(); // adds declare(strict_types=1) + +$namespace = $file->addNamespace('Foo'); +$class = $namespace->addClass('A'); +$class->addMethod('hello'); + +echo $file; + +// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); +``` + +Result: + +```php +dump($var); // prints ['a', 'b', 123] +``` diff --git a/vendor/nette/php-generator/src/PhpGenerator/ClassType.php b/vendor/nette/php-generator/src/PhpGenerator/ClassType.php new file mode 100644 index 0000000..5b3fd59 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/ClassType.php @@ -0,0 +1,510 @@ + Constant */ + private $consts = []; + + /** @var Property[] name => Property */ + private $properties = []; + + /** @var Method[] name => Method */ + private $methods = []; + + + /** + * @param string|object $class + * @return static + */ + public static function from($class): self + { + return (new Factory)->fromClassReflection(new \ReflectionClass($class)); + } + + + public function __construct(string $name = null, PhpNamespace $namespace = null) + { + $this->setName($name); + $this->namespace = $namespace; + } + + + public function __toString(): string + { + try { + return (new Printer)->printClass($this, $this->namespace); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + } + } + + + /** + * Deprecated: an object can be in multiple namespaces. + * @deprecated + */ + public function getNamespace(): ?PhpNamespace + { + return $this->namespace; + } + + + /** + * @return static + */ + public function setName(?string $name): self + { + if ($name !== null && !Helpers::isIdentifier($name)) { + throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); + } + $this->name = $name; + return $this; + } + + + public function getName(): ?string + { + return $this->name; + } + + + /** + * @return static + */ + public function setType(string $type): self + { + if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT], true)) { + throw new Nette\InvalidArgumentException('Argument must be class|interface|trait.'); + } + $this->type = $type; + return $this; + } + + + public function getType(): string + { + return $this->type; + } + + + /** + * @return static + */ + public function setFinal(bool $state = true): self + { + $this->final = $state; + return $this; + } + + + public function isFinal(): bool + { + return $this->final; + } + + + /** + * @return static + */ + public function setAbstract(bool $state = true): self + { + $this->abstract = $state; + return $this; + } + + + public function isAbstract(): bool + { + return $this->abstract; + } + + + /** + * @param string|string[] $names + * @return static + */ + public function setExtends($names): self + { + if (!is_string($names) && !is_array($names)) { + throw new Nette\InvalidArgumentException('Argument must be string or string[].'); + } + $this->validateNames((array) $names); + $this->extends = $names; + return $this; + } + + + /** + * @return string|string[] + */ + public function getExtends() + { + return $this->extends; + } + + + /** + * @return static + */ + public function addExtend(string $name): self + { + $this->validateNames([$name]); + $this->extends = (array) $this->extends; + $this->extends[] = $name; + return $this; + } + + + /** + * @param string[] $names + * @return static + */ + public function setImplements(array $names): self + { + $this->validateNames($names); + $this->implements = $names; + return $this; + } + + + /** + * @return string[] + */ + public function getImplements(): array + { + return $this->implements; + } + + + /** + * @return static + */ + public function addImplement(string $name): self + { + $this->validateNames([$name]); + $this->implements[] = $name; + return $this; + } + + + /** + * @param string[] $names + * @return static + */ + public function setTraits(array $names): self + { + $this->validateNames($names); + $this->traits = array_fill_keys($names, []); + return $this; + } + + + /** + * @return string[] + */ + public function getTraits(): array + { + return array_keys($this->traits); + } + + + /** + * @internal + */ + public function getTraitResolutions(): array + { + return $this->traits; + } + + + /** + * @return static + */ + public function addTrait(string $name, array $resolutions = []): self + { + $this->validateNames([$name]); + $this->traits[$name] = $resolutions; + return $this; + } + + + /** + * @param Method|Property|Constant $member + * @return static + */ + public function addMember($member): self + { + if ($member instanceof Method) { + if ($this->type === self::TYPE_INTERFACE) { + $member->setBody(null); + } + $this->methods[$member->getName()] = $member; + + } elseif ($member instanceof Property) { + $this->properties[$member->getName()] = $member; + + } elseif ($member instanceof Constant) { + $this->consts[$member->getName()] = $member; + + } else { + throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant.'); + } + + return $this; + } + + + /** + * @param Constant[]|mixed[] $consts + * @return static + */ + public function setConstants(array $consts): self + { + $this->consts = []; + foreach ($consts as $k => $v) { + $const = $v instanceof Constant ? $v : (new Constant($k))->setValue($v); + $this->consts[$const->getName()] = $const; + } + return $this; + } + + + /** + * @return Constant[] + */ + public function getConstants(): array + { + return $this->consts; + } + + + public function addConstant(string $name, $value): Constant + { + return $this->consts[$name] = (new Constant($name))->setValue($value); + } + + + /** + * @return static + */ + public function removeConstant(string $name): self + { + unset($this->consts[$name]); + return $this; + } + + + /** + * @param Property[] $props + * @return static + */ + public function setProperties(array $props): self + { + $this->properties = []; + foreach ($props as $v) { + if (!$v instanceof Property) { + throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Property[].'); + } + $this->properties[$v->getName()] = $v; + } + return $this; + } + + + /** + * @return Property[] + */ + public function getProperties(): array + { + return $this->properties; + } + + + public function getProperty(string $name): Property + { + if (!isset($this->properties[$name])) { + throw new Nette\InvalidArgumentException("Property '$name' not found."); + } + return $this->properties[$name]; + } + + + /** + * @param string $name without $ + */ + public function addProperty(string $name, $value = null): Property + { + return $this->properties[$name] = (new Property($name))->setValue($value); + } + + + /** + * @param string $name without $ + * @return static + */ + public function removeProperty(string $name): self + { + unset($this->properties[$name]); + return $this; + } + + + public function hasProperty(string $name): bool + { + return isset($this->properties[$name]); + } + + + /** + * @param Method[] $methods + * @return static + */ + public function setMethods(array $methods): self + { + $this->methods = []; + foreach ($methods as $v) { + if (!$v instanceof Method) { + throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Method[].'); + } + $this->methods[$v->getName()] = $v; + } + return $this; + } + + + /** + * @return Method[] + */ + public function getMethods(): array + { + return $this->methods; + } + + + public function getMethod(string $name): Method + { + if (!isset($this->methods[$name])) { + throw new Nette\InvalidArgumentException("Method '$name' not found."); + } + return $this->methods[$name]; + } + + + public function addMethod(string $name): Method + { + $method = new Method($name); + if ($this->type === self::TYPE_INTERFACE) { + $method->setBody(null); + } else { + $method->setVisibility(self::VISIBILITY_PUBLIC); + } + return $this->methods[$name] = $method; + } + + + /** + * @return static + */ + public function removeMethod(string $name): self + { + unset($this->methods[$name]); + return $this; + } + + + public function hasMethod(string $name): bool + { + return isset($this->methods[$name]); + } + + + /** + * @throws Nette\InvalidStateException + */ + public function validate(): void + { + if ($this->abstract && $this->final) { + throw new Nette\InvalidStateException('Class cannot be abstract and final.'); + + } elseif (!$this->name && ($this->abstract || $this->final)) { + throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.'); + } + } + + + private function validateNames(array $names): void + { + foreach ($names as $name) { + if (!Helpers::isNamespaceIdentifier($name, true)) { + throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); + } + } + } + + + public function __clone() + { + $clone = function ($item) { return clone $item; }; + $this->consts = array_map($clone, $this->consts); + $this->properties = array_map($clone, $this->properties); + $this->methods = array_map($clone, $this->methods); + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Closure.php b/vendor/nette/php-generator/src/PhpGenerator/Closure.php new file mode 100644 index 0000000..df3afff --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Closure.php @@ -0,0 +1,73 @@ +fromFunctionReflection(new \ReflectionFunction($closure)); + } + + + public function __toString(): string + { + try { + return (new Printer)->printClosure($this); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + } + } + + + /** + * @param Parameter[] $uses + * @return static + */ + public function setUses(array $uses): self + { + (function (Parameter ...$uses) {})(...$uses); + $this->uses = $uses; + return $this; + } + + + public function getUses(): array + { + return $this->uses; + } + + + public function addUse(string $name): Parameter + { + return $this->uses[] = new Parameter($name); + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Constant.php b/vendor/nette/php-generator/src/PhpGenerator/Constant.php new file mode 100644 index 0000000..cba9e5a --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Constant.php @@ -0,0 +1,43 @@ +value = $val; + return $this; + } + + + public function getValue() + { + return $this->value; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Dumper.php b/vendor/nette/php-generator/src/PhpGenerator/Dumper.php new file mode 100644 index 0000000..4155c48 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Dumper.php @@ -0,0 +1,229 @@ +dumpVar($var, [], 0, $column); + } + + + private function dumpVar(&$var, array $parents = [], int $level = 0, int $column = 0): string + { + if ($var instanceof PhpLiteral) { + return ltrim(Nette\Utils\Strings::indent(trim((string) $var), $level), "\t"); + + } elseif ($var === null) { + return 'null'; + + } elseif (is_string($var)) { + return $this->dumpString($var); + + } elseif (is_array($var)) { + return $this->dumpArray($var, $parents, $level, $column); + + } elseif (is_object($var)) { + return $this->dumpObject($var, $parents, $level); + + } elseif (is_resource($var)) { + throw new Nette\InvalidArgumentException('Cannot dump resource.'); + + } else { + return var_export($var, true); + } + } + + + private function dumpString(string $var): string + { + if (preg_match('#[^\x09\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error()) { + static $table; + if ($table === null) { + foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) { + $table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT); + } + $table['\\'] = '\\\\'; + $table["\r"] = '\r'; + $table["\n"] = '\n'; + $table["\t"] = '\t'; + $table['$'] = '\$'; + $table['"'] = '\"'; + } + return '"' . strtr($var, $table) . '"'; + } + + return "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $var) . "'"; + } + + + private function dumpArray(array &$var, array $parents, int $level, int $column): string + { + if (empty($var)) { + return '[]'; + + } elseif ($level > $this->maxDepth || in_array($var, $parents ?? [], true)) { + throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); + } + + $space = str_repeat("\t", $level); + $outInline = ''; + $outWrapped = "\n$space"; + $parents[] = $var; + $counter = 0; + + foreach ($var as $k => &$v) { + $keyPart = $k === $counter ? '' : $this->dumpVar($k) . ' => '; + $counter = is_int($k) ? max($k + 1, $counter) : $counter; + $outInline .= ($outInline === '' ? '' : ', ') . $keyPart; + $outInline .= $this->dumpVar($v, $parents, 0, $column + strlen($outInline)); + $outWrapped .= "\t" + . $keyPart + . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart)) + . ",\n$space"; + } + + array_pop($parents); + $wrap = strpos($outInline, "\n") !== false || $level * self::INDENT_LENGTH + $column + strlen($outInline) + 3 > $this->wrapLength; // 3 = [], + return '[' . ($wrap ? $outWrapped : $outInline) . ']'; + } + + + private function dumpObject(&$var, array $parents, int $level): string + { + if ($var instanceof \Serializable) { + return 'unserialize(' . $this->dumpString(serialize($var)) . ')'; + + } elseif ($var instanceof \Closure) { + throw new Nette\InvalidArgumentException('Cannot dump closure.'); + } + + $class = get_class($var); + if ((new \ReflectionObject($var))->isAnonymous()) { + throw new Nette\InvalidArgumentException('Cannot dump anonymous class.'); + + } elseif (in_array($class, ['DateTime', 'DateTimeImmutable'], true)) { + return $this->format("new $class(?, new DateTimeZone(?))", $var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName()); + } + + $arr = (array) $var; + $space = str_repeat("\t", $level); + + if ($level > $this->maxDepth || in_array($var, $parents ?? [], true)) { + throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); + } + + $out = "\n"; + $parents[] = $var; + if (method_exists($var, '__sleep')) { + foreach ($var->__sleep() as $v) { + $props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = true; + } + } + + foreach ($arr as $k => &$v) { + if (!isset($props) || isset($props[$k])) { + $out .= "$space\t" + . ($keyPart = $this->dumpVar($k) . ' => ') + . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart)) + . ",\n"; + } + } + + array_pop($parents); + $out .= $space; + return $class === 'stdClass' + ? "(object) [$out]" + : __CLASS__ . "::createObject('$class', [$out])"; + } + + + /** + * Generates PHP statement. + */ + public function format(string $statement, ...$args): string + { + $tokens = preg_split('#(\.\.\.\?|\$\?|->\?|::\?|\\\\\?|\?\*|\?)#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE); + $res = ''; + foreach ($tokens as $n => $token) { + if ($n % 2 === 0) { + $res .= $token; + } elseif ($token === '\\?') { + $res .= '?'; + } elseif (!$args) { + throw new Nette\InvalidArgumentException('Insufficient number of arguments.'); + } elseif ($token === '?') { + $res .= $this->dump(array_shift($args), strlen($res) - strrpos($res, "\n")); + } elseif ($token === '...?' || $token === '?*') { + $arg = array_shift($args); + if (!is_array($arg)) { + throw new Nette\InvalidArgumentException('Argument must be an array.'); + } + $res .= $this->dumpArguments($arg, strlen($res) - strrpos($res, "\n")); + + } else { // $ -> :: + $arg = array_shift($args); + if ($arg instanceof PhpLiteral || !Helpers::isIdentifier($arg)) { + $arg = '{' . $this->dumpVar($arg) . '}'; + } + $res .= substr($token, 0, -1) . $arg; + } + } + if ($args) { + throw new Nette\InvalidArgumentException('Insufficient number of placeholders.'); + } + return $res; + } + + + private function dumpArguments(array &$var, int $column): string + { + $outInline = $outWrapped = ''; + + foreach ($var as &$v) { + $outInline .= $outInline === '' ? '' : ', '; + $outInline .= $this->dumpVar($v, [$var], 0, $column + strlen($outInline)); + $outWrapped .= ($outWrapped === '' ? '' : ',') . "\n\t" . $this->dumpVar($v, [$var], 1); + } + + return count($var) > 1 && (strpos($outInline, "\n") !== false || $column + strlen($outInline) > $this->wrapLength) + ? $outWrapped . "\n" + : $outInline; + } + + + /** + * @return object + * @internal + */ + public static function createObject(string $class, array $props) + { + return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1)); + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Factory.php b/vendor/nette/php-generator/src/PhpGenerator/Factory.php new file mode 100644 index 0000000..0585d9e --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Factory.php @@ -0,0 +1,140 @@ +isAnonymous() + ? new ClassType + : new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName())); + $class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS)); + $class->setFinal($from->isFinal() && $class->getType() === $class::TYPE_CLASS); + $class->setAbstract($from->isAbstract() && $class->getType() === $class::TYPE_CLASS); + + $ifaces = $from->getInterfaceNames(); + foreach ($ifaces as $iface) { + $ifaces = array_filter($ifaces, function (string $item) use ($iface): bool { + return !is_subclass_of($iface, $item); + }); + } + $class->setImplements($ifaces); + + $class->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); + if ($from->getParentClass()) { + $class->setExtends($from->getParentClass()->getName()); + $class->setImplements(array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames())); + } + $props = $methods = []; + foreach ($from->getProperties() as $prop) { + if ($prop->isDefault() && $prop->getDeclaringClass()->getName() === $from->getName()) { + $props[] = $this->fromPropertyReflection($prop); + } + } + $class->setProperties($props); + foreach ($from->getMethods() as $method) { + if ($method->getDeclaringClass()->getName() === $from->getName()) { + $methods[] = $this->fromMethodReflection($method); + } + } + $class->setMethods($methods); + $class->setConstants($from->getConstants()); + return $class; + } + + + public function fromMethodReflection(\ReflectionMethod $from): Method + { + $method = new Method($from->getName()); + $method->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters())); + $method->setStatic($from->isStatic()); + $isInterface = $from->getDeclaringClass()->isInterface(); + $method->setVisibility($from->isPrivate() + ? ClassType::VISIBILITY_PRIVATE + : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ($isInterface ? null : ClassType::VISIBILITY_PUBLIC)) + ); + $method->setFinal($from->isFinal()); + $method->setAbstract($from->isAbstract() && !$isInterface); + $method->setBody($from->isAbstract() ? null : ''); + $method->setReturnReference($from->returnsReference()); + $method->setVariadic($from->isVariadic()); + $method->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); + if ($from->hasReturnType()) { + $method->setReturnType($from->getReturnType()->getName()); + $method->setReturnNullable($from->getReturnType()->allowsNull()); + } + return $method; + } + + + /** + * @return GlobalFunction|Closure + */ + public function fromFunctionReflection(\ReflectionFunction $from) + { + $function = $from->isClosure() ? new Closure : new GlobalFunction($from->getName()); + $function->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters())); + $function->setReturnReference($from->returnsReference()); + $function->setVariadic($from->isVariadic()); + if (!$from->isClosure()) { + $function->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); + } + if ($from->hasReturnType()) { + $function->setReturnType($from->getReturnType()->getName()); + $function->setReturnNullable($from->getReturnType()->allowsNull()); + } + return $function; + } + + + public function fromParameterReflection(\ReflectionParameter $from): Parameter + { + $param = new Parameter($from->getName()); + $param->setReference($from->isPassedByReference()); + $param->setType($from->hasType() ? $from->getType()->getName() : null); + $param->setNullable($from->hasType() && $from->getType()->allowsNull()); + if ($from->isDefaultValueAvailable()) { + $param->setDefaultValue($from->isDefaultValueConstant() + ? new PhpLiteral($from->getDefaultValueConstantName()) + : $from->getDefaultValue()); + $param->setNullable($param->isNullable() && $param->getDefaultValue() !== null); + } + return $param; + } + + + public function fromPropertyReflection(\ReflectionProperty $from): Property + { + $defaults = $from->getDeclaringClass()->getDefaultProperties(); + $prop = new Property($from->getName()); + $prop->setValue($defaults[$prop->getName()] ?? null); + $prop->setStatic($from->isStatic()); + $prop->setVisibility($from->isPrivate() + ? ClassType::VISIBILITY_PRIVATE + : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC) + ); + if (PHP_VERSION_ID >= 70400 && ($type = $from->getType())) { + $prop->setType($type->getName()); + $prop->setNullable($type->allowsNull()); + $prop->setInitialized(array_key_exists($prop->getName(), $defaults)); + } + $prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); + return $prop; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/GlobalFunction.php b/vendor/nette/php-generator/src/PhpGenerator/GlobalFunction.php new file mode 100644 index 0000000..ca73f6e --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/GlobalFunction.php @@ -0,0 +1,47 @@ +fromFunctionReflection(new \ReflectionFunction($function)); + } + + + public function __toString(): string + { + try { + return (new Printer)->printFunction($this); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + } + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Helpers.php b/vendor/nette/php-generator/src/PhpGenerator/Helpers.php new file mode 100644 index 0000000..70d9db7 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Helpers.php @@ -0,0 +1,100 @@ +dump($var); + } + + + /** @deprecated use Nette\PhpGenerator\Dumper::format() */ + public static function format(string $statement, ...$args): string + { + return (new Dumper)->format($statement, ...$args); + } + + + /** @deprecated use Nette\PhpGenerator\Dumper::format() */ + public static function formatArgs(string $statement, array $args): string + { + return (new Dumper)->format($statement, ...$args); + } + + + public static function formatDocComment(string $content): string + { + if (($s = trim($content)) === '') { + return ''; + } elseif (strpos($content, "\n") === false) { + return "/** $s */\n"; + } else { + return str_replace("\n", "\n * ", "/**\n$s") . "\n */\n"; + } + } + + + public static function unformatDocComment(string $comment): string + { + return preg_replace('#^\s*\* ?#m', '', trim(trim(trim($comment), '/*'))); + } + + + public static function isIdentifier($value): bool + { + return is_string($value) && preg_match('#^' . self::PHP_IDENT . '$#D', $value); + } + + + public static function isNamespaceIdentifier($value, bool $allowLeadingSlash = false): bool + { + $re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::PHP_IDENT . '(\\\\' . self::PHP_IDENT . ')*$#D'; + return is_string($value) && preg_match($re, $value); + } + + + public static function extractNamespace(string $name): string + { + return ($pos = strrpos($name, '\\')) ? substr($name, 0, $pos) : ''; + } + + + public static function extractShortName(string $name): string + { + return ($pos = strrpos($name, '\\')) === false ? $name : substr($name, $pos + 1); + } + + + public static function tabsToSpaces(string $s, int $count = 4): string + { + return str_replace("\t", str_repeat(' ', $count), $s); + } + + + /** @internal */ + public static function createObject(string $class, array $props) + { + return Dumper::createObject($class, $props); + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Method.php b/vendor/nette/php-generator/src/PhpGenerator/Method.php new file mode 100644 index 0000000..262e8cd --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Method.php @@ -0,0 +1,137 @@ +fromMethodReflection(Nette\Utils\Callback::toReflection($method)); + } + + + public function __toString(): string + { + try { + return (new Printer)->printMethod($this); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + } + } + + + /** + * @return static + */ + public function setBody(?string $code, array $args = null): self + { + $this->body = $args === null || $code === null ? $code : (new Dumper)->format($code, ...$args); + return $this; + } + + + public function getBody(): ?string + { + return $this->body; + } + + + /** + * @return static + */ + public function setStatic(bool $state = true): self + { + $this->static = $state; + return $this; + } + + + public function isStatic(): bool + { + return $this->static; + } + + + /** + * @return static + */ + public function setFinal(bool $state = true): self + { + $this->final = $state; + return $this; + } + + + public function isFinal(): bool + { + return $this->final; + } + + + /** + * @return static + */ + public function setAbstract(bool $state = true): self + { + $this->abstract = $state; + return $this; + } + + + public function isAbstract(): bool + { + return $this->abstract; + } + + + /** + * @throws Nette\InvalidStateException + */ + public function validate(): void + { + if ($this->abstract && ($this->final || $this->visibility === ClassType::VISIBILITY_PRIVATE)) { + throw new Nette\InvalidStateException('Method cannot be abstract and final or private.'); + } + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Parameter.php b/vendor/nette/php-generator/src/PhpGenerator/Parameter.php new file mode 100644 index 0000000..2bde9d0 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Parameter.php @@ -0,0 +1,137 @@ +reference = $state; + return $this; + } + + + public function isReference(): bool + { + return $this->reference; + } + + + /** + * @return static + */ + public function setType(?string $type): self + { + $this->type = $type; + return $this; + } + + + public function getType(): ?string + { + return $this->type; + } + + + /** @deprecated use setType() */ + public function setTypeHint(?string $type): self + { + $this->type = $type; + return $this; + } + + + /** @deprecated use getType() */ + public function getTypeHint(): ?string + { + return $this->type; + } + + + /** + * @deprecated just use setDefaultValue() + * @return static + */ + public function setOptional(bool $state = true): self + { + trigger_error(__METHOD__ . '() is deprecated, use setDefaultValue()', E_USER_DEPRECATED); + $this->hasDefaultValue = $state; + return $this; + } + + + /** + * @return static + */ + public function setNullable(bool $state = true): self + { + $this->nullable = $state; + return $this; + } + + + public function isNullable(): bool + { + return $this->nullable; + } + + + /** + * @return static + */ + public function setDefaultValue($val): self + { + $this->defaultValue = $val; + $this->hasDefaultValue = true; + return $this; + } + + + public function getDefaultValue() + { + return $this->defaultValue; + } + + + public function hasDefaultValue(): bool + { + return $this->hasDefaultValue; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/PhpFile.php b/vendor/nette/php-generator/src/PhpGenerator/PhpFile.php new file mode 100644 index 0000000..57c0fe3 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/PhpFile.php @@ -0,0 +1,125 @@ +addNamespace(Helpers::extractNamespace($name)) + ->addClass(Helpers::extractShortName($name)); + } + + + public function addInterface(string $name): ClassType + { + return $this + ->addNamespace(Helpers::extractNamespace($name)) + ->addInterface(Helpers::extractShortName($name)); + } + + + public function addTrait(string $name): ClassType + { + return $this + ->addNamespace(Helpers::extractNamespace($name)) + ->addTrait(Helpers::extractShortName($name)); + } + + + public function addNamespace(string $name): PhpNamespace + { + if (!isset($this->namespaces[$name])) { + $this->namespaces[$name] = new PhpNamespace($name); + foreach ($this->namespaces as $namespace) { + $namespace->setBracketedSyntax(count($this->namespaces) > 1 && isset($this->namespaces[''])); + } + } + return $this->namespaces[$name]; + } + + + /** + * @return PhpNamespace[] + */ + public function getNamespaces(): array + { + return $this->namespaces; + } + + + /** + * @return static + */ + public function addUse(string $name, string $alias = null): self + { + $this->addNamespace('')->addUse($name, $alias); + return $this; + } + + + /** + * Adds declare(strict_types=1) to output. + * @return static + */ + public function setStrictTypes(bool $on = true): self + { + $this->strictTypes = $on; + return $this; + } + + + public function hasStrictTypes(): bool + { + return $this->strictTypes; + } + + + /** @deprecated use hasStrictTypes() */ + public function getStrictTypes(): bool + { + return $this->strictTypes; + } + + + public function __toString(): string + { + try { + return (new Printer)->printFile($this); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + } + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/PhpLiteral.php b/vendor/nette/php-generator/src/PhpGenerator/PhpLiteral.php new file mode 100644 index 0000000..b7dd7d9 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/PhpLiteral.php @@ -0,0 +1,32 @@ +value = $value; + } + + + public function __toString(): string + { + return $this->value; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/PhpNamespace.php b/vendor/nette/php-generator/src/PhpGenerator/PhpNamespace.php new file mode 100644 index 0000000..bfc4d73 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/PhpNamespace.php @@ -0,0 +1,209 @@ + 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, + 'callable' => 1, 'iterable' => 1, 'void' => 1, 'self' => 1, 'parent' => 1, + ]; + + /** @var string */ + private $name; + + /** @var bool */ + private $bracketedSyntax = false; + + /** @var string[] */ + private $uses = []; + + /** @var ClassType[] */ + private $classes = []; + + + public function __construct(string $name) + { + if ($name !== '' && !Helpers::isNamespaceIdentifier($name)) { + throw new Nette\InvalidArgumentException("Value '$name' is not valid name."); + } + $this->name = $name; + } + + + public function getName(): string + { + return $this->name; + } + + + /** + * @return static + * @internal + */ + public function setBracketedSyntax(bool $state = true): self + { + $this->bracketedSyntax = $state; + return $this; + } + + + public function hasBracketedSyntax(): bool + { + return $this->bracketedSyntax; + } + + + /** @deprecated use hasBracketedSyntax() */ + public function getBracketedSyntax(): bool + { + return $this->bracketedSyntax; + } + + + /** + * @throws InvalidStateException + * @return static + */ + public function addUse(string $name, string $alias = null, string &$aliasOut = null): self + { + $name = ltrim($name, '\\'); + if ($alias === null && $this->name === Helpers::extractNamespace($name)) { + $alias = Helpers::extractShortName($name); + } + if ($alias === null) { + $path = explode('\\', $name); + $counter = null; + do { + if (empty($path)) { + $counter++; + } else { + $alias = array_pop($path) . $alias; + } + } while (isset($this->uses[$alias . $counter]) && $this->uses[$alias . $counter] !== $name); + $alias .= $counter; + + } elseif (isset($this->uses[$alias]) && $this->uses[$alias] !== $name) { + throw new InvalidStateException( + "Alias '$alias' used already for '{$this->uses[$alias]}', cannot use for '{$name}'." + ); + } + + $aliasOut = $alias; + $this->uses[$alias] = $name; + asort($this->uses); + return $this; + } + + + /** + * @return string[] + */ + public function getUses(): array + { + return $this->uses; + } + + + public function unresolveName(string $name): string + { + if (isset(self::KEYWORDS[strtolower($name)]) || $name === '') { + return $name; + } + $name = ltrim($name, '\\'); + $res = null; + $lower = strtolower($name); + foreach ($this->uses as $alias => $original) { + if (Strings::startsWith($lower . '\\', strtolower($original) . '\\')) { + $short = $alias . substr($name, strlen($original)); + if (!isset($res) || strlen($res) > strlen($short)) { + $res = $short; + } + } + } + + if (!$res && Strings::startsWith($lower, strtolower($this->name) . '\\')) { + return substr($name, strlen($this->name) + 1); + } else { + return $res ?: ($this->name ? '\\' : '') . $name; + } + } + + + /** + * @return static + */ + public function add(ClassType $class): self + { + $name = $class->getName(); + if ($name === null) { + throw new Nette\InvalidArgumentException('Class does not have a name.'); + } + $this->addUse($this->name . '\\' . $name); + $this->classes[$name] = $class; + return $this; + } + + + public function addClass(string $name): ClassType + { + $this->add($class = new ClassType($name, $this)); + return $class; + } + + + public function addInterface(string $name): ClassType + { + return $this->addClass($name)->setType(ClassType::TYPE_INTERFACE); + } + + + public function addTrait(string $name): ClassType + { + return $this->addClass($name)->setType(ClassType::TYPE_TRAIT); + } + + + /** + * @return ClassType[] + */ + public function getClasses(): array + { + return $this->classes; + } + + + public function __toString(): string + { + try { + return (new Printer)->printNamespace($this); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + } + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Printer.php b/vendor/nette/php-generator/src/PhpGenerator/Printer.php new file mode 100644 index 0000000..5ec2757 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Printer.php @@ -0,0 +1,275 @@ +getComment() . "\n") + . 'function ' + . ($function->getReturnReference() ? '&' : '') + . $function->getName() + . $this->printParameters($function, $namespace) + . $this->printReturnType($function, $namespace) + . "\n{\n" . $this->indent(ltrim(rtrim($function->getBody()) . "\n")) . "}\n"; + } + + + public function printClosure(Closure $closure): string + { + $uses = []; + foreach ($closure->getUses() as $param) { + $uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName(); + } + $useStr = strlen($tmp = implode(', ', $uses)) > (new Dumper)->wrapLength && count($uses) > 1 + ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n" + : $tmp; + + return 'function ' + . ($closure->getReturnReference() ? '&' : '') + . $this->printParameters($closure, null) + . ($uses ? " use ($useStr)" : '') + . $this->printReturnType($closure, null) + . " {\n" . $this->indent(ltrim(rtrim($closure->getBody()) . "\n")) . '}'; + } + + + public function printArrowFunction(Closure $closure): string + { + foreach ($closure->getUses() as $use) { + if ($use->isReference()) { + throw new Nette\InvalidArgumentException('Arrow function cannot bind variables by-reference.'); + } + } + + return 'fn ' + . ($closure->getReturnReference() ? '&' : '') + . $this->printParameters($closure, null) + . $this->printReturnType($closure, null) + . ' => ' . trim($closure->getBody()) . ';'; + } + + + public function printMethod(Method $method, PhpNamespace $namespace = null): string + { + $method->validate(); + return Helpers::formatDocComment($method->getComment() . "\n") + . ($method->isAbstract() ? 'abstract ' : '') + . ($method->isFinal() ? 'final ' : '') + . ($method->getVisibility() ? $method->getVisibility() . ' ' : '') + . ($method->isStatic() ? 'static ' : '') + . 'function ' + . ($method->getReturnReference() ? '&' : '') + . $method->getName() + . ($params = $this->printParameters($method, $namespace)) + . $this->printReturnType($method, $namespace) + . ($method->isAbstract() || $method->getBody() === null + ? ";\n" + : (strpos($params, "\n") === false ? "\n" : ' ') + . "{\n" + . $this->indent(ltrim(rtrim($method->getBody()) . "\n")) + . "}\n"); + } + + + public function printClass(ClassType $class, PhpNamespace $namespace = null): string + { + $class->validate(); + $resolver = $this->resolveTypes && $namespace ? [$namespace, 'unresolveName'] : function ($s) { return $s; }; + + $traits = []; + foreach ($class->getTraitResolutions() as $trait => $resolutions) { + $traits[] = 'use ' . $resolver($trait) + . ($resolutions ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" : ";\n"); + } + + $consts = []; + foreach ($class->getConstants() as $const) { + $def = ($const->getVisibility() ? $const->getVisibility() . ' ' : '') . 'const ' . $const->getName() . ' = '; + $consts[] = Helpers::formatDocComment((string) $const->getComment()) + . $def + . $this->dump($const->getValue(), strlen($def)) . ";\n"; + } + + $properties = []; + foreach ($class->getProperties() as $property) { + $type = $property->getType(); + $def = (($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') . ' ' + . ($type ? ($property->isNullable() ? '?' : '') . ($this->resolveTypes && $namespace ? $namespace->unresolveName($type) : $type) . ' ' : '') + . '$' . $property->getName()); + + $properties[] = Helpers::formatDocComment((string) $property->getComment()) + . $def + . ($property->getValue() === null && !$property->isInitialized() ? '' : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = ' + . ";\n"; + } + + $methods = []; + foreach ($class->getMethods() as $method) { + $methods[] = $this->printMethod($method, $namespace); + } + + $members = array_filter([ + implode('', $traits), + implode('', $consts), + implode("\n", $properties), + ($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '') + . implode(str_repeat("\n", $this->linesBetweenMethods), $methods), + ]); + + return Strings::normalize( + Helpers::formatDocComment($class->getComment() . "\n") + . ($class->isAbstract() ? 'abstract ' : '') + . ($class->isFinal() ? 'final ' : '') + . ($class->getName() ? $class->getType() . ' ' . $class->getName() . ' ' : '') + . ($class->getExtends() ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' ' : '') + . ($class->getImplements() ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) . ' ' : '') + . ($class->getName() ? "\n" : '') . "{\n" + . ($members ? $this->indent(implode("\n", $members)) : '') + . '}' + ) . ($class->getName() ? "\n" : ''); + } + + + public function printNamespace(PhpNamespace $namespace): string + { + $name = $namespace->getName(); + $uses = $this->printUses($namespace); + + $classes = []; + foreach ($namespace->getClasses() as $class) { + $classes[] = $this->printClass($class, $namespace); + } + + $body = ($uses ? $uses . "\n\n" : '') + . implode("\n", $classes); + + if ($namespace->hasBracketedSyntax()) { + return 'namespace' . ($name ? " $name" : '') . "\n{\n" + . $this->indent($body) + . "}\n"; + + } else { + return ($name ? "namespace $name;\n\n" : '') + . $body; + } + } + + + public function printFile(PhpFile $file): string + { + $namespaces = []; + foreach ($file->getNamespaces() as $namespace) { + $namespaces[] = $this->printNamespace($namespace); + } + + return Strings::normalize( + "getComment() ? "\n" . Helpers::formatDocComment($file->getComment() . "\n") : '') + . "\n" + . ($file->hasStrictTypes() ? "declare(strict_types=1);\n\n" : '') + . implode("\n\n", $namespaces) + ) . "\n"; + } + + + /** + * @return static + */ + public function setTypeResolving(bool $state = true): self + { + $this->resolveTypes = $state; + return $this; + } + + + protected function indent(string $s): string + { + $s = str_replace("\t", $this->indentation, $s); + return Strings::indent($s, 1, $this->indentation); + } + + + protected function dump($var, int $column = 0): string + { + return (new Dumper)->dump($var, $column); + } + + + protected function printUses(PhpNamespace $namespace): string + { + $name = $namespace->getName(); + $uses = []; + foreach ($namespace->getUses() as $alias => $original) { + if ($original !== ($name ? $name . '\\' . $alias : $alias)) { + if ($alias === $original || substr($original, -(strlen($alias) + 1)) === '\\' . $alias) { + $uses[] = "use $original;"; + } else { + $uses[] = "use $original as $alias;"; + } + } + } + return implode("\n", $uses); + } + + + /** + * @param Nette\PhpGenerator\Traits\FunctionLike $function + */ + protected function printParameters($function, ?PhpNamespace $namespace): string + { + $params = []; + $list = $function->getParameters(); + foreach ($list as $param) { + $variadic = $function->isVariadic() && $param === end($list); + $type = $param->getType(); + $params[] = ($type ? ($param->isNullable() ? '?' : '') . ($this->resolveTypes && $namespace ? $namespace->unresolveName($type) : $type) . ' ' : '') + . ($param->isReference() ? '&' : '') + . ($variadic ? '...' : '') + . '$' . $param->getName() + . ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : ''); + } + + return strlen($tmp = implode(', ', $params)) > (new Dumper)->wrapLength && count($params) > 1 + ? "(\n" . $this->indentation . implode(",\n" . $this->indentation, $params) . "\n)" + : "($tmp)"; + } + + + /** + * @param Nette\PhpGenerator\Traits\FunctionLike $function + */ + protected function printReturnType($function, ?PhpNamespace $namespace): string + { + return $function->getReturnType() + ? ': ' . ($function->isReturnNullable() ? '?' : '') . ($this->resolveTypes && $namespace ? $namespace->unresolveName($function->getReturnType()) : $function->getReturnType()) + : ''; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Property.php b/vendor/nette/php-generator/src/PhpGenerator/Property.php new file mode 100644 index 0000000..7d1795d --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Property.php @@ -0,0 +1,121 @@ +value = $val; + return $this; + } + + + public function &getValue() + { + return $this->value; + } + + + /** + * @return static + */ + public function setStatic(bool $state = true): self + { + $this->static = $state; + return $this; + } + + + public function isStatic(): bool + { + return $this->static; + } + + + /** + * @return static + */ + public function setType(?string $val): self + { + $this->type = $val; + return $this; + } + + + public function getType(): ?string + { + return $this->type; + } + + + /** + * @return static + */ + public function setNullable(bool $state = true): self + { + $this->nullable = $state; + return $this; + } + + + public function isNullable(): bool + { + return $this->nullable; + } + + + /** + * @return static + */ + public function setInitialized(bool $state = true): self + { + $this->initialized = $state; + return $this; + } + + + public function isInitialized(): bool + { + return $this->initialized; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/PsrPrinter.php b/vendor/nette/php-generator/src/PhpGenerator/PsrPrinter.php new file mode 100644 index 0000000..aef0f7d --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/PsrPrinter.php @@ -0,0 +1,23 @@ +comment = $val; + return $this; + } + + + public function getComment(): ?string + { + return $this->comment; + } + + + /** + * @return static + */ + public function addComment(string $val): self + { + $this->comment .= $this->comment ? "\n$val" : $val; + return $this; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php b/vendor/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php new file mode 100644 index 0000000..23859ff --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php @@ -0,0 +1,196 @@ + Parameter */ + private $parameters = []; + + /** @var bool */ + private $variadic = false; + + /** @var string|null */ + private $returnType; + + /** @var bool */ + private $returnReference = false; + + /** @var bool */ + private $returnNullable = false; + + + /** + * @return static + */ + public function setBody(string $code, array $args = null): self + { + $this->body = $args === null ? $code : (new Dumper)->format($code, ...$args); + return $this; + } + + + public function getBody(): string + { + return $this->body; + } + + + /** + * @return static + */ + public function addBody(string $code, array $args = null): self + { + $this->body .= ($args === null ? $code : (new Dumper)->format($code, ...$args)) . "\n"; + return $this; + } + + + /** + * @param Parameter[] $val + * @return static + */ + public function setParameters(array $val): self + { + $this->parameters = []; + foreach ($val as $v) { + if (!$v instanceof Parameter) { + throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Parameter[].'); + } + $this->parameters[$v->getName()] = $v; + } + return $this; + } + + + /** + * @return Parameter[] + */ + public function getParameters(): array + { + return $this->parameters; + } + + + /** + * @param string $name without $ + */ + public function addParameter(string $name, $defaultValue = null): Parameter + { + $param = new Parameter($name); + if (func_num_args() > 1) { + $param->setDefaultValue($defaultValue); + } + return $this->parameters[$name] = $param; + } + + + /** + * @param string $name without $ + * @return static + */ + public function removeParameter(string $name): self + { + unset($this->parameters[$name]); + return $this; + } + + + /** + * @return static + */ + public function setVariadic(bool $state = true): self + { + $this->variadic = $state; + return $this; + } + + + public function isVariadic(): bool + { + return $this->variadic; + } + + + /** + * @return static + */ + public function setReturnType(?string $val): self + { + $this->returnType = $val; + return $this; + } + + + public function getReturnType(): ?string + { + return $this->returnType; + } + + + /** + * @return static + */ + public function setReturnReference(bool $state = true): self + { + $this->returnReference = $state; + return $this; + } + + + public function getReturnReference(): bool + { + return $this->returnReference; + } + + + /** + * @return static + */ + public function setReturnNullable(bool $state = true): self + { + $this->returnNullable = $state; + return $this; + } + + + public function isReturnNullable(): bool + { + return $this->returnNullable; + } + + + /** @deprecated use isReturnNullable() */ + public function getReturnNullable(): bool + { + return $this->returnNullable; + } + + + /** + * @deprecated + */ + public function setNamespace(PhpNamespace $val = null): self + { + trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); + return $this; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Traits/NameAware.php b/vendor/nette/php-generator/src/PhpGenerator/Traits/NameAware.php new file mode 100644 index 0000000..157aa0f --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Traits/NameAware.php @@ -0,0 +1,49 @@ +name = $name; + } + + + public function getName(): string + { + return $this->name; + } + + + /** + * Returns clone with a different name. + * @return static + */ + public function cloneWithName(string $name): self + { + $dolly = clone $this; + $dolly->__construct($name); + return $dolly; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php b/vendor/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php new file mode 100644 index 0000000..5011f02 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php @@ -0,0 +1,43 @@ +visibility = $val; + return $this; + } + + + public function getVisibility(): ?string + { + return $this->visibility; + } +} diff --git a/vendor/nette/utils/.phpstorm.meta.php b/vendor/nette/utils/.phpstorm.meta.php new file mode 100644 index 0000000..b6594ae --- /dev/null +++ b/vendor/nette/utils/.phpstorm.meta.php @@ -0,0 +1,19 @@ +=7.1" + }, + "require-dev": { + "nette/tester": "~2.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-iconv": "to use Strings::webalize() and toAscii()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-xml": "to use Strings::length() etc. when mbstring is not available", + "ext-gd": "to use Image", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "autoload": { + "classmap": ["src/"] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + } +} diff --git a/vendor/nette/utils/contributing.md b/vendor/nette/utils/contributing.md new file mode 100644 index 0000000..184152c --- /dev/null +++ b/vendor/nette/utils/contributing.md @@ -0,0 +1,33 @@ +How to contribute & use the issue tracker +========================================= + +Nette welcomes your contributions. There are several ways to help out: + +* Create an issue on GitHub, if you have found a bug +* Write test cases for open bug issues +* Write fixes for open bug/feature issues, preferably with test cases included +* Contribute to the [documentation](https://nette.org/en/writing) + +Issues +------ + +Please **do not use the issue tracker to ask questions**. We will be happy to help you +on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette). + +A good bug report shouldn't leave others needing to chase you up for more +information. Please try to be as detailed as possible in your report. + +**Feature requests** are welcome. But take a moment to find out whether your idea +fits with the scope and aims of the project. It's up to *you* to make a strong +case to convince the project's developers of the merits of this feature. + +Contributing +------------ + +If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing). + +The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them. + +Please do not fix whitespace, format code, or make a purely cosmetic patch. + +Thanks! :heart: diff --git a/vendor/nette/utils/license.md b/vendor/nette/utils/license.md new file mode 100644 index 0000000..cf741bd --- /dev/null +++ b/vendor/nette/utils/license.md @@ -0,0 +1,60 @@ +Licenses +======== + +Good news! You may use Nette Framework under the terms of either +the New BSD License or the GNU General Public License (GPL) version 2 or 3. + +The BSD License is recommended for most projects. It is easy to understand and it +places almost no restrictions on what you can do with the framework. If the GPL +fits better to your project, you can use the framework under this license. + +You don't have to notify anyone which license you are using. You can freely +use Nette Framework in commercial projects as long as the copyright header +remains intact. + +Please be advised that the name "Nette Framework" is a protected trademark and its +usage has some limitations. So please do not use word "Nette" in the name of your +project or top-level domain, and choose a name that stands on its own merits. +If your stuff is good, it will not take long to establish a reputation for yourselves. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +This software is provided by the copyright holders and contributors "as is" and +any express or implied warranties, including, but not limited to, the implied +warranties of merchantability and fitness for a particular purpose are +disclaimed. In no event shall the copyright owner or contributors be liable for +any direct, indirect, incidental, special, exemplary, or consequential damages +(including, but not limited to, procurement of substitute goods or services; +loss of use, data, or profits; or business interruption) however caused and on +any theory of liability, whether in contract, strict liability, or tort +(including negligence or otherwise) arising in any way out of the use of this +software, even if advised of the possibility of such damage. + + +GNU General Public License +-------------------------- + +GPL licenses are very very long, so instead of including them here we offer +you URLs with full text: + +- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html) +- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html) diff --git a/vendor/nette/utils/readme.md b/vendor/nette/utils/readme.md new file mode 100644 index 0000000..0de6f52 --- /dev/null +++ b/vendor/nette/utils/readme.md @@ -0,0 +1,41 @@ +Nette Utility Classes +===================== + +[![Downloads this Month](https://img.shields.io/packagist/dm/nette/utils.svg)](https://packagist.org/packages/nette/utils) +[![Build Status](https://travis-ci.org/nette/utils.svg?branch=master)](https://travis-ci.org/nette/utils) +[![Coverage Status](https://coveralls.io/repos/github/nette/utils/badge.svg?branch=master)](https://coveralls.io/github/nette/utils?branch=master) +[![Latest Stable Version](https://poser.pugx.org/nette/utils/v/stable)](https://github.com/nette/utils/releases) +[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/utils/blob/master/license.md) + + +Introduction +------------ + +In package nette/utils you will find a set of useful classes for everyday use: + +- [Arrays](https://doc.nette.org/arrays) - manipulate arrays +- [Callback](https://doc.nette.org/callback) - PHP callbacks +- [Date and Time](https://doc.nette.org/datetime) - modify times and dates +- [Filesystem](https://doc.nette.org/filesystem) - copying, renaming, … +- [HTML elements](https://doc.nette.org/html-elements) - generate HTML +- [Images](https://doc.nette.org/images) - crop, resize, rotate images +- [JSON](https://doc.nette.org/json) - encoding and decoding +- [Generating Random Strings](https://doc.nette.org/random) +- [Pagination](https://doc.nette.org/pagination) - comfort pagination +- [Strings](https://doc.nette.org/strings) - useful text transpilers +- [SmartObject](https://doc.nette.org/smartobject) - PHP Object Enhancements +- [Validation](https://doc.nette.org/validators) - validate inputs + +Documentation can be found on the [website](https://doc.nette.org/utils). + + +Installation +------------ + +The recommended way to install is via Composer: + +``` +composer require nette/utils +``` + +It requires PHP version 7.1 and supports PHP up to 7.3. diff --git a/vendor/nette/utils/src/Iterators/CachingIterator.php b/vendor/nette/utils/src/Iterators/CachingIterator.php new file mode 100644 index 0000000..2204e23 --- /dev/null +++ b/vendor/nette/utils/src/Iterators/CachingIterator.php @@ -0,0 +1,166 @@ +getIterator(); + } while ($iterator instanceof \IteratorAggregate); + + } elseif ($iterator instanceof \Traversable) { + if (!$iterator instanceof \Iterator) { + $iterator = new \IteratorIterator($iterator); + } + } else { + throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', __CLASS__, is_object($iterator) ? get_class($iterator) : gettype($iterator))); + } + + parent::__construct($iterator, 0); + } + + + /** + * Is the current element the first one? + */ + public function isFirst(int $gridWidth = null): bool + { + return $this->counter === 1 || ($gridWidth && $this->counter !== 0 && (($this->counter - 1) % $gridWidth) === 0); + } + + + /** + * Is the current element the last one? + */ + public function isLast(int $gridWidth = null): bool + { + return !$this->hasNext() || ($gridWidth && ($this->counter % $gridWidth) === 0); + } + + + /** + * Is the iterator empty? + */ + public function isEmpty(): bool + { + return $this->counter === 0; + } + + + /** + * Is the counter odd? + */ + public function isOdd(): bool + { + return $this->counter % 2 === 1; + } + + + /** + * Is the counter even? + */ + public function isEven(): bool + { + return $this->counter % 2 === 0; + } + + + /** + * Returns the counter. + */ + public function getCounter(): int + { + return $this->counter; + } + + + /** + * Returns the count of elements. + */ + public function count(): int + { + $inner = $this->getInnerIterator(); + if ($inner instanceof \Countable) { + return $inner->count(); + + } else { + throw new Nette\NotSupportedException('Iterator is not countable.'); + } + } + + + /** + * Forwards to the next element. + */ + public function next(): void + { + parent::next(); + if (parent::valid()) { + $this->counter++; + } + } + + + /** + * Rewinds the Iterator. + */ + public function rewind(): void + { + parent::rewind(); + $this->counter = parent::valid() ? 1 : 0; + } + + + /** + * Returns the next key. + * @return mixed + */ + public function getNextKey() + { + return $this->getInnerIterator()->key(); + } + + + /** + * Returns the next element. + * @return mixed + */ + public function getNextValue() + { + return $this->getInnerIterator()->current(); + } +} diff --git a/vendor/nette/utils/src/Iterators/Mapper.php b/vendor/nette/utils/src/Iterators/Mapper.php new file mode 100644 index 0000000..fec8086 --- /dev/null +++ b/vendor/nette/utils/src/Iterators/Mapper.php @@ -0,0 +1,34 @@ +callback = $callback; + } + + + public function current() + { + return ($this->callback)(parent::current(), parent::key()); + } +} diff --git a/vendor/nette/utils/src/Utils/ArrayHash.php b/vendor/nette/utils/src/Utils/ArrayHash.php new file mode 100644 index 0000000..55237a4 --- /dev/null +++ b/vendor/nette/utils/src/Utils/ArrayHash.php @@ -0,0 +1,94 @@ + $value) { + if ($recursive && is_array($value)) { + $obj->$key = static::from($value, true); + } else { + $obj->$key = $value; + } + } + return $obj; + } + + + /** + * Returns an iterator over all items. + */ + public function getIterator(): \RecursiveArrayIterator + { + return new \RecursiveArrayIterator((array) $this); + } + + + /** + * Returns items count. + */ + public function count(): int + { + return count((array) $this); + } + + + /** + * Replaces or appends a item. + */ + public function offsetSet($key, $value): void + { + if (!is_scalar($key)) { // prevents null + throw new Nette\InvalidArgumentException(sprintf('Key must be either a string or an integer, %s given.', gettype($key))); + } + $this->$key = $value; + } + + + /** + * Returns a item. + * @return mixed + */ + public function offsetGet($key) + { + return $this->$key; + } + + + /** + * Determines whether a item exists. + */ + public function offsetExists($key): bool + { + return isset($this->$key); + } + + + /** + * Removes the element from this list. + */ + public function offsetUnset($key): void + { + unset($this->$key); + } +} diff --git a/vendor/nette/utils/src/Utils/ArrayList.php b/vendor/nette/utils/src/Utils/ArrayList.php new file mode 100644 index 0000000..1a638fa --- /dev/null +++ b/vendor/nette/utils/src/Utils/ArrayList.php @@ -0,0 +1,110 @@ +list); + } + + + /** + * Returns items count. + */ + public function count(): int + { + return count($this->list); + } + + + /** + * Replaces or appends a item. + * @param int|null $index + * @throws Nette\OutOfRangeException + */ + public function offsetSet($index, $value): void + { + if ($index === null) { + $this->list[] = $value; + + } elseif (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + + } else { + $this->list[$index] = $value; + } + } + + + /** + * Returns a item. + * @param int $index + * @return mixed + * @throws Nette\OutOfRangeException + */ + public function offsetGet($index) + { + if (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + } + return $this->list[$index]; + } + + + /** + * Determines whether a item exists. + * @param int $index + */ + public function offsetExists($index): bool + { + return is_int($index) && $index >= 0 && $index < count($this->list); + } + + + /** + * Removes the element at the specified position in this list. + * @param int $index + * @throws Nette\OutOfRangeException + */ + public function offsetUnset($index): void + { + if (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + } + array_splice($this->list, $index, 1); + } + + + /** + * Prepends a item. + */ + public function prepend($value): void + { + $first = array_slice($this->list, 0, 1); + $this->offsetSet(0, $value); + array_splice($this->list, 1, 0, $first); + } +} diff --git a/vendor/nette/utils/src/Utils/Arrays.php b/vendor/nette/utils/src/Utils/Arrays.php new file mode 100644 index 0000000..d1c64a6 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Arrays.php @@ -0,0 +1,300 @@ + $v) { + if (is_array($v) && is_array($arr2[$k])) { + $res[$k] = self::mergeTree($v, $arr2[$k]); + } + } + return $res; + } + + + /** + * Searches the array for a given key and returns the offset if successful. + * @return int|null offset if it is found, null otherwise + */ + public static function searchKey(array $arr, $key): ?int + { + $foo = [$key => null]; + return ($tmp = array_search(key($foo), array_keys($arr), true)) === false ? null : $tmp; + } + + + /** + * Inserts new array before item specified by key. + */ + public static function insertBefore(array &$arr, $key, array $inserted): void + { + $offset = (int) self::searchKey($arr, $key); + $arr = array_slice($arr, 0, $offset, true) + $inserted + array_slice($arr, $offset, count($arr), true); + } + + + /** + * Inserts new array after item specified by key. + */ + public static function insertAfter(array &$arr, $key, array $inserted): void + { + $offset = self::searchKey($arr, $key); + $offset = $offset === null ? count($arr) : $offset + 1; + $arr = array_slice($arr, 0, $offset, true) + $inserted + array_slice($arr, $offset, count($arr), true); + } + + + /** + * Renames key in array. + */ + public static function renameKey(array &$arr, $oldKey, $newKey): void + { + $offset = self::searchKey($arr, $oldKey); + if ($offset !== null) { + $keys = array_keys($arr); + $keys[$offset] = $newKey; + $arr = array_combine($keys, $arr); + } + } + + + /** + * Returns array entries that match the pattern. + */ + public static function grep(array $arr, string $pattern, int $flags = 0): array + { + return Strings::pcre('preg_grep', [$pattern, $arr, $flags]); + } + + + /** + * Returns flattened array. + */ + public static function flatten(array $arr, bool $preserveKeys = false): array + { + $res = []; + $cb = $preserveKeys + ? function ($v, $k) use (&$res): void { $res[$k] = $v; } + : function ($v) use (&$res): void { $res[] = $v; }; + array_walk_recursive($arr, $cb); + return $res; + } + + + /** + * Finds whether a variable is a zero-based integer indexed array. + */ + public static function isList($value): bool + { + return is_array($value) && (!$value || array_keys($value) === range(0, count($value) - 1)); + } + + + /** + * Reformats table to associative tree. Path looks like 'field|field[]field->field=field'. + * @return array|\stdClass + */ + public static function associate(array $arr, $path) + { + $parts = is_array($path) + ? $path + : preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + + if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') { + throw new Nette\InvalidArgumentException("Invalid path '$path'."); + } + + $res = $parts[0] === '->' ? new \stdClass : []; + + foreach ($arr as $rowOrig) { + $row = (array) $rowOrig; + $x = &$res; + + for ($i = 0; $i < count($parts); $i++) { + $part = $parts[$i]; + if ($part === '[]') { + $x = &$x[]; + + } elseif ($part === '=') { + if (isset($parts[++$i])) { + $x = $row[$parts[$i]]; + $row = null; + } + + } elseif ($part === '->') { + if (isset($parts[++$i])) { + if ($x === null) { + $x = new \stdClass; + } + $x = &$x->{$row[$parts[$i]]}; + } else { + $row = is_object($rowOrig) ? $rowOrig : (object) $row; + } + + } elseif ($part !== '|') { + $x = &$x[(string) $row[$part]]; + } + } + + if ($x === null) { + $x = $row; + } + } + + return $res; + } + + + /** + * Normalizes to associative array. + */ + public static function normalize(array $arr, $filling = null): array + { + $res = []; + foreach ($arr as $k => $v) { + $res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v; + } + return $res; + } + + + /** + * Picks element from the array by key and return its value. + * @param string|int $key array key + * @return mixed + * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided + */ + public static function pick(array &$arr, $key, $default = null) + { + if (array_key_exists($key, $arr)) { + $value = $arr[$key]; + unset($arr[$key]); + return $value; + + } elseif (func_num_args() < 3) { + throw new Nette\InvalidArgumentException("Missing item '$key'."); + + } else { + return $default; + } + } + + + /** + * Tests whether some element in the array passes the callback test. + */ + public static function some(array $arr, callable $callback): bool + { + foreach ($arr as $k => $v) { + if ($callback($v, $k, $arr)) { + return true; + } + } + return false; + } + + + /** + * Tests whether all elements in the array pass the callback test. + */ + public static function every(array $arr, callable $callback): bool + { + foreach ($arr as $k => $v) { + if (!$callback($v, $k, $arr)) { + return false; + } + } + return true; + } + + + /** + * Applies the callback to the elements of the array. + */ + public static function map(array $arr, callable $callback): array + { + $res = []; + foreach ($arr as $k => $v) { + $res[$k] = $callback($v, $k, $arr); + } + return $res; + } + + + /** + * Converts array to object + * @param object $obj + * @return object + */ + public static function toObject(array $arr, $obj) + { + foreach ($arr as $k => $v) { + $obj->$k = $v; + } + return $obj; + } +} diff --git a/vendor/nette/utils/src/Utils/Callback.php b/vendor/nette/utils/src/Utils/Callback.php new file mode 100644 index 0000000..8f997e4 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Callback.php @@ -0,0 +1,164 @@ +getMessage()); + } + } + + + /** + * Invokes callback. + * @return mixed + * @deprecated + */ + public static function invoke($callable, ...$args) + { + trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED); + self::check($callable); + return $callable(...$args); + } + + + /** + * Invokes callback with an array of parameters. + * @return mixed + * @deprecated + */ + public static function invokeArgs($callable, array $args = []) + { + trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED); + self::check($callable); + return $callable(...$args); + } + + + /** + * Invokes internal PHP function with own error handler. + * @return mixed + */ + public static function invokeSafe(string $function, array $args, callable $onError) + { + $prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool { + if ($file === __FILE__) { + $msg = $message; + if (ini_get('html_errors')) { + $msg = html_entity_decode(strip_tags($msg)); + } + $msg = preg_replace("#^$function\(.*?\): #", '', $msg); + if ($onError($msg, $severity) !== false) { + return null; + } + } + return $prev ? $prev(...func_get_args()) : false; + }); + + try { + return $function(...$args); + } finally { + restore_error_handler(); + } + } + + + /** + * @return callable + */ + public static function check($callable, bool $syntax = false) + { + if (!is_callable($callable, $syntax)) { + throw new Nette\InvalidArgumentException($syntax + ? 'Given value is not a callable type.' + : sprintf("Callback '%s' is not callable.", self::toString($callable)) + ); + } + return $callable; + } + + + public static function toString($callable): string + { + if ($callable instanceof \Closure) { + $inner = self::unwrap($callable); + return '{closure' . ($inner instanceof \Closure ? '}' : ' ' . self::toString($inner) . '}'); + } elseif (is_string($callable) && $callable[0] === "\0") { + return '{lambda}'; + } else { + is_callable(is_object($callable) ? [$callable, '__invoke'] : $callable, true, $textual); + return $textual; + } + } + + + public static function toReflection($callable): \ReflectionFunctionAbstract + { + if ($callable instanceof \Closure) { + $callable = self::unwrap($callable); + } + + if (is_string($callable) && strpos($callable, '::')) { + return new \ReflectionMethod($callable); + } elseif (is_array($callable)) { + return new \ReflectionMethod($callable[0], $callable[1]); + } elseif (is_object($callable) && !$callable instanceof \Closure) { + return new \ReflectionMethod($callable, '__invoke'); + } else { + return new \ReflectionFunction($callable); + } + } + + + public static function isStatic(callable $callable): bool + { + return is_array($callable) ? is_string($callable[0]) : is_string($callable); + } + + + /** + * Unwraps closure created by Closure::fromCallable() + * @internal + */ + public static function unwrap(\Closure $closure): callable + { + $r = new \ReflectionFunction($closure); + if (substr($r->getName(), -1) === '}') { + return $closure; + + } elseif ($obj = $r->getClosureThis()) { + return [$obj, $r->getName()]; + + } elseif ($class = $r->getClosureScopeClass()) { + return [$class->getName(), $r->getName()]; + + } else { + return $r->getName(); + } + } +} diff --git a/vendor/nette/utils/src/Utils/DateTime.php b/vendor/nette/utils/src/Utils/DateTime.php new file mode 100644 index 0000000..d2fd8bb --- /dev/null +++ b/vendor/nette/utils/src/Utils/DateTime.php @@ -0,0 +1,124 @@ +format('Y-m-d H:i:s.u'), $time->getTimezone()); + + } elseif (is_numeric($time)) { + if ($time <= self::YEAR) { + $time += time(); + } + return (new static('@' . $time))->setTimezone(new \DateTimeZone(date_default_timezone_get())); + + } else { // textual or null + return new static($time); + } + } + + + /** + * Creates DateTime object. + * @return static + */ + public static function fromParts(int $year, int $month, int $day, int $hour = 0, int $minute = 0, float $second = 0.0) + { + $s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5f', $year, $month, $day, $hour, $minute, $second); + if (!checkdate($month, $day, $year) || $hour < 0 || $hour > 23 || $minute < 0 || $minute > 59 || $second < 0 || $second >= 60) { + throw new Nette\InvalidArgumentException("Invalid date '$s'"); + } + return new static($s); + } + + + /** + * Returns new DateTime object formatted according to the specified format. + * @param string $format The format the $time parameter should be in + * @param string $time + * @param string|\DateTimeZone $timezone (default timezone is used if null is passed) + * @return static|false + */ + public static function createFromFormat($format, $time, $timezone = null) + { + if ($timezone === null) { + $timezone = new \DateTimeZone(date_default_timezone_get()); + + } elseif (is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + + } elseif (!$timezone instanceof \DateTimeZone) { + throw new Nette\InvalidArgumentException('Invalid timezone given'); + } + + $date = parent::createFromFormat($format, $time, $timezone); + return $date ? static::from($date) : false; + } + + + /** + * Returns JSON representation in ISO 8601 (used by JavaScript). + */ + public function jsonSerialize(): string + { + return $this->format('c'); + } + + + public function __toString(): string + { + return $this->format('Y-m-d H:i:s'); + } + + + /** + * @return static + */ + public function modifyClone(string $modify = '') + { + $dolly = clone $this; + return $modify ? $dolly->modify($modify) : $dolly; + } +} diff --git a/vendor/nette/utils/src/Utils/FileSystem.php b/vendor/nette/utils/src/Utils/FileSystem.php new file mode 100644 index 0000000..25101ac --- /dev/null +++ b/vendor/nette/utils/src/Utils/FileSystem.php @@ -0,0 +1,159 @@ +getPathname()); + } + foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) { + if ($item->isDir()) { + static::createDir($dest . '/' . $iterator->getSubPathName()); + } else { + static::copy($item->getPathname(), $dest . '/' . $iterator->getSubPathName()); + } + } + + } else { + static::createDir(dirname($dest)); + if (($s = @fopen($source, 'rb')) && ($d = @fopen($dest, 'wb')) && @stream_copy_to_stream($s, $d) === false) { // @ is escalated to exception + throw new Nette\IOException("Unable to copy file '$source' to '$dest'. " . self::getLastError()); + } + } + } + + + /** + * Deletes a file or directory. + * @throws Nette\IOException + */ + public static function delete(string $path): void + { + if (is_file($path) || is_link($path)) { + $func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink'; + if (!@$func($path)) { // @ is escalated to exception + throw new Nette\IOException("Unable to delete '$path'. " . self::getLastError()); + } + + } elseif (is_dir($path)) { + foreach (new \FilesystemIterator($path) as $item) { + static::delete($item->getPathname()); + } + if (!@rmdir($path)) { // @ is escalated to exception + throw new Nette\IOException("Unable to delete directory '$path'. " . self::getLastError()); + } + } + } + + + /** + * Renames a file or directory. + * @throws Nette\IOException + * @throws Nette\InvalidStateException if the target file or directory already exist + */ + public static function rename(string $name, string $newName, bool $overwrite = true): void + { + if (!$overwrite && file_exists($newName)) { + throw new Nette\InvalidStateException("File or directory '$newName' already exists."); + + } elseif (!file_exists($name)) { + throw new Nette\IOException("File or directory '$name' not found."); + + } else { + static::createDir(dirname($newName)); + if (realpath($name) !== realpath($newName)) { + static::delete($newName); + } + if (!@rename($name, $newName)) { // @ is escalated to exception + throw new Nette\IOException("Unable to rename file or directory '$name' to '$newName'. " . self::getLastError()); + } + } + } + + + /** + * Reads file content. + * @throws Nette\IOException + */ + public static function read(string $file): string + { + $content = @file_get_contents($file); // @ is escalated to exception + if ($content === false) { + throw new Nette\IOException("Unable to read file '$file'. " . self::getLastError()); + } + return $content; + } + + + /** + * Writes a string to a file. + * @throws Nette\IOException + */ + public static function write(string $file, string $content, ?int $mode = 0666): void + { + static::createDir(dirname($file)); + if (@file_put_contents($file, $content) === false) { // @ is escalated to exception + throw new Nette\IOException("Unable to write file '$file'. " . self::getLastError()); + } + if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception + throw new Nette\IOException("Unable to chmod file '$file'. " . self::getLastError()); + } + } + + + /** + * Is path absolute? + */ + public static function isAbsolute(string $path): bool + { + return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path); + } + + + private static function getLastError(): string + { + return preg_replace('#^\w+\(.*?\): #', '', error_get_last()['message']); + } +} diff --git a/vendor/nette/utils/src/Utils/Html.php b/vendor/nette/utils/src/Utils/Html.php new file mode 100644 index 0000000..508adc9 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Html.php @@ -0,0 +1,614 @@ + + * $el = Html::el('a')->href($link)->setText('Nette'); + * $el->class = 'myclass'; + * echo $el; + * + * echo $el->startTag(), $el->endTag(); + * + */ +class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString +{ + use Nette\SmartObject; + + /** @var array element's attributes */ + public $attrs = []; + + /** @var bool use XHTML syntax? */ + public static $xhtml = false; + + /** @var array empty (void) elements */ + public static $emptyElements = [ + 'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1, + 'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1, + 'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1, + ]; + + /** @var array of Html | string nodes */ + protected $children = []; + + /** @var string element's name */ + private $name; + + /** @var bool is element empty? */ + private $isEmpty; + + + /** + * Static factory. + * @param array|string $attrs element's attributes or plain text content + * @return static + */ + public static function el(string $name = null, $attrs = null) + { + $el = new static; + $parts = explode(' ', (string) $name, 2); + $el->setName($parts[0]); + + if (is_array($attrs)) { + $el->attrs = $attrs; + + } elseif ($attrs !== null) { + $el->setText($attrs); + } + + if (isset($parts[1])) { + foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\2|\s))?#i') as $m) { + $el->attrs[$m[1]] = $m[3] ?? true; + } + } + + return $el; + } + + + /** + * Changes element's name. + * @return static + */ + final public function setName(string $name, bool $isEmpty = null) + { + $this->name = $name; + $this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]); + return $this; + } + + + /** + * Returns element's name. + */ + final public function getName(): string + { + return $this->name; + } + + + /** + * Is element empty? + */ + final public function isEmpty(): bool + { + return $this->isEmpty; + } + + + /** + * Sets multiple attributes. + * @return static + */ + public function addAttributes(array $attrs) + { + $this->attrs = array_merge($this->attrs, $attrs); + return $this; + } + + + /** + * Appends value to element's attribute. + * @return static + */ + public function appendAttribute(string $name, $value, $option = true) + { + if (is_array($value)) { + $prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : []; + $this->attrs[$name] = $value + $prev; + + } elseif ((string) $value === '') { + $tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists + + } elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array + $this->attrs[$name][$value] = $option; + + } else { + $this->attrs[$name] = [$this->attrs[$name] => true, $value => $option]; + } + return $this; + } + + + /** + * Sets element's attribute. + * @return static + */ + public function setAttribute(string $name, $value) + { + $this->attrs[$name] = $value; + return $this; + } + + + /** + * Returns element's attribute. + * @return mixed + */ + public function getAttribute(string $name) + { + return $this->attrs[$name] ?? null; + } + + + /** + * Unsets element's attribute. + * @return static + */ + public function removeAttribute(string $name) + { + unset($this->attrs[$name]); + return $this; + } + + + /** + * Unsets element's attributes. + * @return static + */ + public function removeAttributes(array $attributes) + { + foreach ($attributes as $name) { + unset($this->attrs[$name]); + } + return $this; + } + + + /** + * Overloaded setter for element's attribute. + */ + final public function __set(string $name, $value): void + { + $this->attrs[$name] = $value; + } + + + /** + * Overloaded getter for element's attribute. + * @return mixed + */ + final public function &__get(string $name) + { + return $this->attrs[$name]; + } + + + /** + * Overloaded tester for element's attribute. + */ + final public function __isset(string $name): bool + { + return isset($this->attrs[$name]); + } + + + /** + * Overloaded unsetter for element's attribute. + */ + final public function __unset(string $name): void + { + unset($this->attrs[$name]); + } + + + /** + * Overloaded setter for element's attribute. + * @return mixed + */ + final public function __call(string $m, array $args) + { + $p = substr($m, 0, 3); + if ($p === 'get' || $p === 'set' || $p === 'add') { + $m = substr($m, 3); + $m[0] = $m[0] | "\x20"; + if ($p === 'get') { + return $this->attrs[$m] ?? null; + + } elseif ($p === 'add') { + $args[] = true; + } + } + + if (count($args) === 0) { // invalid + + } elseif (count($args) === 1) { // set + $this->attrs[$m] = $args[0]; + + } else { // add + $this->appendAttribute($m, $args[0], $args[1]); + } + + return $this; + } + + + /** + * Special setter for element's attribute. + * @return static + */ + final public function href(string $path, array $query = null) + { + if ($query) { + $query = http_build_query($query, '', '&'); + if ($query !== '') { + $path .= '?' . $query; + } + } + $this->attrs['href'] = $path; + return $this; + } + + + /** + * Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'. + * @return static + */ + public function data(string $name, $value = null) + { + if (func_num_args() === 1) { + $this->attrs['data'] = $name; + } else { + $this->attrs["data-$name"] = is_bool($value) ? json_encode($value) : $value; + } + return $this; + } + + + /** + * Sets element's HTML content. + * @param IHtmlString|string $html + * @return static + */ + final public function setHtml($html) + { + $this->children = [(string) $html]; + return $this; + } + + + /** + * Returns element's HTML content. + */ + final public function getHtml(): string + { + return implode('', $this->children); + } + + + /** + * Sets element's textual content. + * @param IHtmlString|string|int|float $text + * @return static + */ + final public function setText($text) + { + if (!$text instanceof IHtmlString) { + $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); + } + $this->children = [(string) $text]; + return $this; + } + + + /** + * Returns element's textual content. + */ + final public function getText(): string + { + return html_entity_decode(strip_tags($this->getHtml()), ENT_QUOTES, 'UTF-8'); + } + + + /** + * Adds new element's child. + * @param IHtmlString|string $child Html node or raw HTML string + * @return static + */ + final public function addHtml($child) + { + return $this->insert(null, $child); + } + + + /** + * Appends plain-text string to element content. + * @param IHtmlString|string|int|float $text + * @return static + */ + public function addText($text) + { + if (!$text instanceof IHtmlString) { + $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); + } + return $this->insert(null, $text); + } + + + /** + * Creates and adds a new Html child. + * @param array|string $attrs element's attributes or raw HTML string + * @return static created element + */ + final public function create(string $name, $attrs = null) + { + $this->insert(null, $child = static::el($name, $attrs)); + return $child; + } + + + /** + * Inserts child node. + * @param IHtmlString|string $child Html node or raw HTML string + * @return static + */ + public function insert(?int $index, $child, bool $replace = false) + { + $child = $child instanceof self ? $child : (string) $child; + if ($index === null) { // append + $this->children[] = $child; + + } else { // insert or replace + array_splice($this->children, $index, $replace ? 1 : 0, [$child]); + } + + return $this; + } + + + /** + * Inserts (replaces) child node (\ArrayAccess implementation). + * @param int|null $index position or null for appending + * @param Html|string $child Html node or raw HTML string + */ + final public function offsetSet($index, $child): void + { + $this->insert($index, $child, true); + } + + + /** + * Returns child node (\ArrayAccess implementation). + * @param int $index + * @return static|string + */ + final public function offsetGet($index) + { + return $this->children[$index]; + } + + + /** + * Exists child node? (\ArrayAccess implementation). + * @param int $index + */ + final public function offsetExists($index): bool + { + return isset($this->children[$index]); + } + + + /** + * Removes child node (\ArrayAccess implementation). + * @param int $index + */ + public function offsetUnset($index): void + { + if (isset($this->children[$index])) { + array_splice($this->children, $index, 1); + } + } + + + /** + * Returns children count. + */ + final public function count(): int + { + return count($this->children); + } + + + /** + * Removes all children. + */ + public function removeChildren(): void + { + $this->children = []; + } + + + /** + * Iterates over elements. + */ + final public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->children); + } + + + /** + * Returns all children. + */ + final public function getChildren(): array + { + return $this->children; + } + + + /** + * Renders element's start tag, content and end tag. + */ + final public function render(int $indent = null): string + { + $s = $this->startTag(); + + if (!$this->isEmpty) { + // add content + if ($indent !== null) { + $indent++; + } + foreach ($this->children as $child) { + if ($child instanceof self) { + $s .= $child->render($indent); + } else { + $s .= $child; + } + } + + // add end tag + $s .= $this->endTag(); + } + + if ($indent !== null) { + return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2)); + } + return $s; + } + + + final public function __toString(): string + { + try { + return $this->render(); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + return ''; + } + } + + + /** + * Returns element's start tag. + */ + final public function startTag(): string + { + return $this->name + ? '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>') + : ''; + } + + + /** + * Returns element's end tag. + */ + final public function endTag(): string + { + return $this->name && !$this->isEmpty ? 'name . '>' : ''; + } + + + /** + * Returns element's attributes. + * @internal + */ + final public function attributes(): string + { + if (!is_array($this->attrs)) { + return ''; + } + + $s = ''; + $attrs = $this->attrs; + foreach ($attrs as $key => $value) { + if ($value === null || $value === false) { + continue; + + } elseif ($value === true) { + if (static::$xhtml) { + $s .= ' ' . $key . '="' . $key . '"'; + } else { + $s .= ' ' . $key; + } + continue; + + } elseif (is_array($value)) { + if (strncmp($key, 'data-', 5) === 0) { + $value = Json::encode($value); + + } else { + $tmp = null; + foreach ($value as $k => $v) { + if ($v != null) { // intentionally ==, skip nulls & empty string + // composite 'style' vs. 'others' + $tmp[] = $v === true ? $k : (is_string($k) ? $k . ':' . $v : $v); + } + } + if ($tmp === null) { + continue; + } + + $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp); + } + + } elseif (is_float($value)) { + $value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.'); + + } else { + $value = (string) $value; + } + + $q = strpos($value, '"') === false ? '"' : "'"; + $s .= ' ' . $key . '=' . $q + . str_replace( + ['&', $q, '<'], + ['&', $q === '"' ? '"' : ''', self::$xhtml ? '<' : '<'], + $value + ) + . (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '') + . $q; + } + + $s = str_replace('@', '@', $s); + return $s; + } + + + /** + * Clones all children too. + */ + public function __clone() + { + foreach ($this->children as $key => $value) { + if (is_object($value)) { + $this->children[$key] = clone $value; + } + } + } +} diff --git a/vendor/nette/utils/src/Utils/IHtmlString.php b/vendor/nette/utils/src/Utils/IHtmlString.php new file mode 100644 index 0000000..80f570e --- /dev/null +++ b/vendor/nette/utils/src/Utils/IHtmlString.php @@ -0,0 +1,20 @@ + + * $image = Image::fromFile('nette.jpg'); + * $image->resize(150, 100); + * $image->sharpen(); + * $image->send(); + * + * + * @method void alphaBlending(bool $on) + * @method void antialias(bool $on) + * @method void arc($x, $y, $w, $h, $start, $end, $color) + * @method void char(int $font, $x, $y, string $char, $color) + * @method void charUp(int $font, $x, $y, string $char, $color) + * @method int colorAllocate($red, $green, $blue) + * @method int colorAllocateAlpha($red, $green, $blue, $alpha) + * @method int colorAt($x, $y) + * @method int colorClosest($red, $green, $blue) + * @method int colorClosestAlpha($red, $green, $blue, $alpha) + * @method int colorClosestHWB($red, $green, $blue) + * @method void colorDeallocate($color) + * @method int colorExact($red, $green, $blue) + * @method int colorExactAlpha($red, $green, $blue, $alpha) + * @method void colorMatch(Image $image2) + * @method int colorResolve($red, $green, $blue) + * @method int colorResolveAlpha($red, $green, $blue, $alpha) + * @method void colorSet($index, $red, $green, $blue) + * @method array colorsForIndex($index) + * @method int colorsTotal() + * @method int colorTransparent($color = null) + * @method void convolution(array $matrix, float $div, float $offset) + * @method void copy(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH) + * @method void copyMerge(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity) + * @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity) + * @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH) + * @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH) + * @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1) + * @method void dashedLine($x1, $y1, $x2, $y2, $color) + * @method void ellipse($cx, $cy, $w, $h, $color) + * @method void fill($x, $y, $color) + * @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style) + * @method void filledEllipse($cx, $cy, $w, $h, $color) + * @method void filledPolygon(array $points, $numPoints, $color) + * @method void filledRectangle($x1, $y1, $x2, $y2, $color) + * @method void fillToBorder($x, $y, $border, $color) + * @method void filter($filtertype) + * @method void flip(int $mode) + * @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = null) + * @method void gammaCorrect(float $inputgamma, float $outputgamma) + * @method int interlace($interlace = null) + * @method bool isTrueColor() + * @method void layerEffect($effect) + * @method void line($x1, $y1, $x2, $y2, $color) + * @method void paletteCopy(Image $source) + * @method void paletteToTrueColor() + * @method void polygon(array $points, $numPoints, $color) + * @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = null, $tightness = null, float $angle = null, $antialiasSteps = null) + * @method void rectangle($x1, $y1, $x2, $y2, $col) + * @method Image rotate(float $angle, $backgroundColor) + * @method void saveAlpha(bool $saveflag) + * @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED) + * @method void setBrush(Image $brush) + * @method void setPixel($x, $y, $color) + * @method void setStyle(array $style) + * @method void setThickness($thickness) + * @method void setTile(Image $tile) + * @method void string($font, $x, $y, string $s, $col) + * @method void stringUp($font, $x, $y, string $s, $col) + * @method void trueColorToPalette(bool $dither, $ncolors) + * @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text) + * @property-read int $width + * @property-read int $height + * @property-read resource $imageResource + */ +class Image +{ + use Nette\SmartObject; + + /** {@link resize()} only shrinks images */ + public const SHRINK_ONLY = 0b0001; + + /** {@link resize()} will ignore aspect ratio */ + public const STRETCH = 0b0010; + + /** {@link resize()} fits in given area so its dimensions are less than or equal to the required dimensions */ + public const FIT = 0b0000; + + /** {@link resize()} fills given area so its dimensions are greater than or equal to the required dimensions */ + public const FILL = 0b0100; + + /** {@link resize()} fills given area exactly */ + public const EXACT = 0b1000; + + /** image types */ + public const + JPEG = IMAGETYPE_JPEG, + PNG = IMAGETYPE_PNG, + GIF = IMAGETYPE_GIF, + WEBP = 18; // IMAGETYPE_WEBP is available as of PHP 7.1 + + public const EMPTY_GIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;"; + + private const FORMATS = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp']; + + /** @var resource */ + private $image; + + + /** + * Returns RGB color (0..255) and transparency (0..127). + */ + public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array + { + return [ + 'red' => max(0, min(255, $red)), + 'green' => max(0, min(255, $green)), + 'blue' => max(0, min(255, $blue)), + 'alpha' => max(0, min(127, $transparency)), + ]; + } + + + /** + * Opens image from file. + * @throws Nette\NotSupportedException if gd extension is not loaded + * @throws UnknownImageFileException if file not found or file type is not known + * @return static + */ + public static function fromFile(string $file, int &$detectedFormat = null) + { + if (!extension_loaded('gd')) { + throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); + } + + $detectedFormat = @getimagesize($file)[2]; // @ - files smaller than 12 bytes causes read error + if (!isset(self::FORMATS[$detectedFormat])) { + $detectedFormat = null; + throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found."); + } + return new static(Callback::invokeSafe('imagecreatefrom' . image_type_to_extension($detectedFormat, false), [$file], function (string $message): void { + throw new ImageException($message); + })); + } + + + /** + * Create a new image from the image stream in the string. + * @return static + * @throws ImageException + */ + public static function fromString(string $s, int &$detectedFormat = null) + { + if (!extension_loaded('gd')) { + throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); + } + + if (func_num_args() > 1) { + $tmp = @getimagesizefromstring($s)[2]; // @ - strings smaller than 12 bytes causes read error + $detectedFormat = isset(self::FORMATS[$tmp]) ? $tmp : null; + } + + return new static(Callback::invokeSafe('imagecreatefromstring', [$s], function (string $message): void { + throw new ImageException($message); + })); + } + + + /** + * Creates blank image. + * @return static + */ + public static function fromBlank(int $width, int $height, array $color = null) + { + if (!extension_loaded('gd')) { + throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); + } + + if ($width < 1 || $height < 1) { + throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.'); + } + + $image = imagecreatetruecolor($width, $height); + if ($color) { + $color += ['alpha' => 0]; + $color = imagecolorresolvealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']); + imagealphablending($image, false); + imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color); + imagealphablending($image, true); + } + return new static($image); + } + + + /** + * Wraps GD image. + * @param resource $image + */ + public function __construct($image) + { + $this->setImageResource($image); + imagesavealpha($image, true); + } + + + /** + * Returns image width. + */ + public function getWidth(): int + { + return imagesx($this->image); + } + + + /** + * Returns image height. + */ + public function getHeight(): int + { + return imagesy($this->image); + } + + + /** + * Sets image resource. + * @param resource $image + * @return static + */ + protected function setImageResource($image) + { + if (!is_resource($image) || get_resource_type($image) !== 'gd') { + throw new Nette\InvalidArgumentException('Image is not valid.'); + } + $this->image = $image; + return $this; + } + + + /** + * Returns image GD resource. + * @return resource + */ + public function getImageResource() + { + return $this->image; + } + + + /** + * Resizes image. + * @param int|string $width in pixels or percent + * @param int|string $height in pixels or percent + * @return static + */ + public function resize($width, $height, int $flags = self::FIT) + { + if ($flags & self::EXACT) { + return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height); + } + + [$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $flags); + + if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize + $newImage = static::fromBlank($newWidth, $newHeight, self::rgb(0, 0, 0, 127))->getImageResource(); + imagecopyresampled( + $newImage, $this->image, + 0, 0, 0, 0, + $newWidth, $newHeight, $this->getWidth(), $this->getHeight() + ); + $this->image = $newImage; + } + + if ($width < 0 || $height < 0) { + imageflip($this->image, $width < 0 ? ($height < 0 ? IMG_FLIP_BOTH : IMG_FLIP_HORIZONTAL) : IMG_FLIP_VERTICAL); + } + return $this; + } + + + /** + * Calculates dimensions of resized image. + * @param int|string $newWidth in pixels or percent + * @param int|string $newHeight in pixels or percent + */ + public static function calculateSize(int $srcWidth, int $srcHeight, $newWidth, $newHeight, int $flags = self::FIT): array + { + if (is_string($newWidth) && substr($newWidth, -1) === '%') { + $newWidth = (int) round($srcWidth / 100 * abs(substr($newWidth, 0, -1))); + $percents = true; + } else { + $newWidth = (int) abs($newWidth); + } + + if (is_string($newHeight) && substr($newHeight, -1) === '%') { + $newHeight = (int) round($srcHeight / 100 * abs(substr($newHeight, 0, -1))); + $flags |= empty($percents) ? 0 : self::STRETCH; + } else { + $newHeight = (int) abs($newHeight); + } + + if ($flags & self::STRETCH) { // non-proportional + if (empty($newWidth) || empty($newHeight)) { + throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.'); + } + + if ($flags & self::SHRINK_ONLY) { + $newWidth = (int) round($srcWidth * min(1, $newWidth / $srcWidth)); + $newHeight = (int) round($srcHeight * min(1, $newHeight / $srcHeight)); + } + + } else { // proportional + if (empty($newWidth) && empty($newHeight)) { + throw new Nette\InvalidArgumentException('At least width or height must be specified.'); + } + + $scale = []; + if ($newWidth > 0) { // fit width + $scale[] = $newWidth / $srcWidth; + } + + if ($newHeight > 0) { // fit height + $scale[] = $newHeight / $srcHeight; + } + + if ($flags & self::FILL) { + $scale = [max($scale)]; + } + + if ($flags & self::SHRINK_ONLY) { + $scale[] = 1; + } + + $scale = min($scale); + $newWidth = (int) round($srcWidth * $scale); + $newHeight = (int) round($srcHeight * $scale); + } + + return [max($newWidth, 1), max($newHeight, 1)]; + } + + + /** + * Crops image. + * @param int|string $left in pixels or percent + * @param int|string $top in pixels or percent + * @param int|string $width in pixels or percent + * @param int|string $height in pixels or percent + * @return static + */ + public function crop($left, $top, $width, $height) + { + [$r['x'], $r['y'], $r['width'], $r['height']] + = static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height); + $this->image = imagecrop($this->image, $r); + imagesavealpha($this->image, true); + return $this; + } + + + /** + * Calculates dimensions of cutout in image. + * @param int|string $left in pixels or percent + * @param int|string $top in pixels or percent + * @param int|string $newWidth in pixels or percent + * @param int|string $newHeight in pixels or percent + */ + public static function calculateCutout(int $srcWidth, int $srcHeight, $left, $top, $newWidth, $newHeight): array + { + if (is_string($newWidth) && substr($newWidth, -1) === '%') { + $newWidth = (int) round($srcWidth / 100 * substr($newWidth, 0, -1)); + } + if (is_string($newHeight) && substr($newHeight, -1) === '%') { + $newHeight = (int) round($srcHeight / 100 * substr($newHeight, 0, -1)); + } + if (is_string($left) && substr($left, -1) === '%') { + $left = (int) round(($srcWidth - $newWidth) / 100 * substr($left, 0, -1)); + } + if (is_string($top) && substr($top, -1) === '%') { + $top = (int) round(($srcHeight - $newHeight) / 100 * substr($top, 0, -1)); + } + if ($left < 0) { + $newWidth += $left; + $left = 0; + } + if ($top < 0) { + $newHeight += $top; + $top = 0; + } + $newWidth = min($newWidth, $srcWidth - $left); + $newHeight = min($newHeight, $srcHeight - $top); + return [$left, $top, $newWidth, $newHeight]; + } + + + /** + * Sharpen image. + * @return static + */ + public function sharpen() + { + imageconvolution($this->image, [ // my magic numbers ;) + [-1, -1, -1], + [-1, 24, -1], + [-1, -1, -1], + ], 16, 0); + return $this; + } + + + /** + * Puts another image into this image. + * @param int|string $left in pixels or percent + * @param int|string $top in pixels or percent + * @param int $opacity 0..100 + * @return static + */ + public function place(self $image, $left = 0, $top = 0, int $opacity = 100) + { + $opacity = max(0, min(100, $opacity)); + if ($opacity === 0) { + return $this; + } + + $width = $image->getWidth(); + $height = $image->getHeight(); + + if (is_string($left) && substr($left, -1) === '%') { + $left = (int) round(($this->getWidth() - $width) / 100 * substr($left, 0, -1)); + } + + if (is_string($top) && substr($top, -1) === '%') { + $top = (int) round(($this->getHeight() - $height) / 100 * substr($top, 0, -1)); + } + + $output = $input = $image->image; + if ($opacity < 100) { + $tbl = []; + for ($i = 0; $i < 128; $i++) { + $tbl[$i] = round(127 - (127 - $i) * $opacity / 100); + } + + $output = imagecreatetruecolor($width, $height); + imagealphablending($output, false); + if (!$image->isTrueColor()) { + $input = $output; + imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127)); + imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height); + } + for ($x = 0; $x < $width; $x++) { + for ($y = 0; $y < $height; $y++) { + $c = \imagecolorat($input, $x, $y); + $c = ($c & 0xFFFFFF) + ($tbl[$c >> 24] << 24); + \imagesetpixel($output, $x, $y, $c); + } + } + imagealphablending($output, true); + } + + imagecopy( + $this->image, $output, + $left, $top, 0, 0, $width, $height + ); + return $this; + } + + + /** + * Saves image to the file. Quality is 0..100 for JPEG and WEBP, 0..9 for PNG. + * @throws ImageException + */ + public function save(string $file, int $quality = null, int $type = null): void + { + if ($type === null) { + $extensions = array_flip(self::FORMATS) + ['jpg' => self::JPEG]; + $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + if (!isset($extensions[$ext])) { + throw new Nette\InvalidArgumentException("Unsupported file extension '$ext'."); + } + $type = $extensions[$ext]; + } + + $this->output($type, $quality, $file); + } + + + /** + * Outputs image to string. Quality is 0..100 for JPEG and WEBP, 0..9 for PNG. + */ + public function toString(int $type = self::JPEG, int $quality = null): string + { + ob_start(function () {}); + $this->output($type, $quality); + return ob_get_clean(); + } + + + /** + * Outputs image to string. + */ + public function __toString(): string + { + try { + return $this->toString(); + } catch (\Throwable $e) { + if (func_num_args() || PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + return ''; + } + } + + + /** + * Outputs image to browser. Quality is 0..100 for JPEG and WEBP, 0..9 for PNG. + * @throws ImageException + */ + public function send(int $type = self::JPEG, int $quality = null): void + { + if (!isset(self::FORMATS[$type])) { + throw new Nette\InvalidArgumentException("Unsupported image type '$type'."); + } + header('Content-Type: ' . image_type_to_mime_type($type)); + $this->output($type, $quality); + } + + + /** + * Outputs image to browser or file. + * @throws ImageException + */ + private function output(int $type, ?int $quality, string $file = null): void + { + switch ($type) { + case self::JPEG: + $quality = $quality === null ? 85 : max(0, min(100, $quality)); + $success = imagejpeg($this->image, $file, $quality); + break; + + case self::PNG: + $quality = $quality === null ? 9 : max(0, min(9, $quality)); + $success = imagepng($this->image, $file, $quality); + break; + + case self::GIF: + $success = imagegif($this->image, $file); + break; + + case self::WEBP: + $quality = $quality === null ? 80 : max(0, min(100, $quality)); + $success = imagewebp($this->image, $file, $quality); + break; + + default: + throw new Nette\InvalidArgumentException("Unsupported image type '$type'."); + } + if (!$success) { + throw new ImageException(error_get_last()['message'] ?: 'Unknown error'); + } + } + + + /** + * Call to undefined method. + * @return mixed + * @throws Nette\MemberAccessException + */ + public function __call(string $name, array $args) + { + $function = 'image' . $name; + if (!function_exists($function)) { + ObjectHelpers::strictCall(get_class($this), $name); + } + + foreach ($args as $key => $value) { + if ($value instanceof self) { + $args[$key] = $value->getImageResource(); + + } elseif (is_array($value) && isset($value['red'])) { // rgb + $args[$key] = imagecolorallocatealpha( + $this->image, + $value['red'], $value['green'], $value['blue'], $value['alpha'] + ) ?: imagecolorresolvealpha( + $this->image, + $value['red'], $value['green'], $value['blue'], $value['alpha'] + ); + } + } + $res = $function($this->image, ...$args); + return is_resource($res) && get_resource_type($res) === 'gd' ? $this->setImageResource($res) : $res; + } + + + public function __clone() + { + ob_start(function () {}); + imagegd2($this->image); + $this->setImageResource(imagecreatefromstring(ob_get_clean())); + } + + + /** + * Prevents serialization. + */ + public function __sleep(): array + { + throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.'); + } +} diff --git a/vendor/nette/utils/src/Utils/Json.php b/vendor/nette/utils/src/Utils/Json.php new file mode 100644 index 0000000..a10ed07 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Json.php @@ -0,0 +1,60 @@ +getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m') + ), $name); + throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } + + + /** + * @throws MemberAccessException + */ + public static function strictSet(string $class, string $name): void + { + $rc = new \ReflectionClass($class); + $hint = self::getSuggestion(array_merge( + array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m') + ), $name); + throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } + + + /** + * @throws MemberAccessException + */ + public static function strictCall(string $class, string $method, array $additionalMethods = []): void + { + $hint = self::getSuggestion(array_merge( + get_class_methods($class), + self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'), + $additionalMethods + ), $method); + + if (method_exists($class, $method)) { // called parent::$method() + $class = 'parent'; + } + throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); + } + + + /** + * @throws MemberAccessException + */ + public static function strictStaticCall(string $class, string $method): void + { + $hint = self::getSuggestion( + array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }), + $method + ); + throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); + } + + + /** + * Returns array of magic properties defined by annotation @property. + * @return array of [name => bit mask] + * @internal + */ + public static function getMagicProperties(string $class): array + { + static $cache; + $props = &$cache[$class]; + if ($props !== null) { + return $props; + } + + $rc = new \ReflectionClass($class); + preg_match_all( + '~^ [ \t*]* @property(|-read|-write) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx', + (string) $rc->getDocComment(), $matches, PREG_SET_ORDER + ); + + $props = []; + foreach ($matches as [, $type, $name]) { + $uname = ucfirst($name); + $write = $type !== '-read' + && $rc->hasMethod($nm = 'set' . $uname) + && ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic(); + $read = $type !== '-write' + && ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname)) + && ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic(); + + if ($read || $write) { + $props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3; + } + } + + foreach ($rc->getTraits() as $trait) { + $props += self::getMagicProperties($trait->getName()); + } + + if ($parent = get_parent_class($class)) { + $props += self::getMagicProperties($parent); + } + return $props; + } + + + /** + * Finds the best suggestion (for 8-bit encoding). + * @param (\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionClass|\ReflectionProperty|string)[] $possibilities + * @internal + */ + public static function getSuggestion(array $possibilities, string $value): ?string + { + $norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '', $value); + $best = null; + $min = (strlen($value) / 4 + 1) * 10 + .1; + foreach (array_unique($possibilities, SORT_REGULAR) as $item) { + $item = $item instanceof \Reflector ? $item->getName() : $item; + if ($item !== $value && ( + ($len = levenshtein($item, $value, 10, 11, 10)) < $min + || ($len = levenshtein(preg_replace($re, '', $item), $norm, 10, 11, 10) + 20) < $min + )) { + $min = $len; + $best = $item; + } + } + return $best; + } + + + private static function parseFullDoc(\ReflectionClass $rc, string $pattern): array + { + do { + $doc[] = $rc->getDocComment(); + $traits = $rc->getTraits(); + while ($trait = array_pop($traits)) { + $doc[] = $trait->getDocComment(); + $traits += $trait->getTraits(); + } + } while ($rc = $rc->getParentClass()); + return preg_match_all($pattern, implode($doc), $m) ? $m[1] : []; + } + + + /** + * Checks if the public non-static property exists. + * @return bool|string returns 'event' if the property exists and has event like name + * @internal + */ + public static function hasProperty(string $class, string $name) + { + static $cache; + $prop = &$cache[$class][$name]; + if ($prop === null) { + $prop = false; + try { + $rp = new \ReflectionProperty($class, $name); + if ($rp->isPublic() && !$rp->isStatic()) { + $prop = $name >= 'onA' && $name < 'on_' ? 'event' : true; + } + } catch (\ReflectionException $e) { + } + } + return $prop; + } +} diff --git a/vendor/nette/utils/src/Utils/ObjectMixin.php b/vendor/nette/utils/src/Utils/ObjectMixin.php new file mode 100644 index 0000000..36559f2 --- /dev/null +++ b/vendor/nette/utils/src/Utils/ObjectMixin.php @@ -0,0 +1,43 @@ +page = $page; + return $this; + } + + + /** + * Returns current page number. + */ + public function getPage(): int + { + return $this->base + $this->getPageIndex(); + } + + + /** + * Returns first page number. + */ + public function getFirstPage(): int + { + return $this->base; + } + + + /** + * Returns last page number. + */ + public function getLastPage(): ?int + { + return $this->itemCount === null ? null : $this->base + max(0, $this->getPageCount() - 1); + } + + + /** + * Sets first page (base) number. + * @return static + */ + public function setBase(int $base) + { + $this->base = $base; + return $this; + } + + + /** + * Returns first page (base) number. + */ + public function getBase(): int + { + return $this->base; + } + + + /** + * Returns zero-based page number. + */ + protected function getPageIndex(): int + { + $index = max(0, $this->page - $this->base); + return $this->itemCount === null ? $index : min($index, max(0, $this->getPageCount() - 1)); + } + + + /** + * Is the current page the first one? + */ + public function isFirst(): bool + { + return $this->getPageIndex() === 0; + } + + + /** + * Is the current page the last one? + */ + public function isLast(): bool + { + return $this->itemCount === null ? false : $this->getPageIndex() >= $this->getPageCount() - 1; + } + + + /** + * Returns the total number of pages. + */ + public function getPageCount(): ?int + { + return $this->itemCount === null ? null : (int) ceil($this->itemCount / $this->itemsPerPage); + } + + + /** + * Sets the number of items to display on a single page. + * @return static + */ + public function setItemsPerPage(int $itemsPerPage) + { + $this->itemsPerPage = max(1, $itemsPerPage); + return $this; + } + + + /** + * Returns the number of items to display on a single page. + */ + public function getItemsPerPage(): int + { + return $this->itemsPerPage; + } + + + /** + * Sets the total number of items. + * @return static + */ + public function setItemCount(int $itemCount = null) + { + $this->itemCount = $itemCount === null ? null : max(0, $itemCount); + return $this; + } + + + /** + * Returns the total number of items. + */ + public function getItemCount(): ?int + { + return $this->itemCount; + } + + + /** + * Returns the absolute index of the first item on current page. + */ + public function getOffset(): int + { + return $this->getPageIndex() * $this->itemsPerPage; + } + + + /** + * Returns the absolute index of the first item on current page in countdown paging. + */ + public function getCountdownOffset(): ?int + { + return $this->itemCount === null + ? null + : max(0, $this->itemCount - ($this->getPageIndex() + 1) * $this->itemsPerPage); + } + + + /** + * Returns the number of items on current page. + */ + public function getLength(): int + { + return $this->itemCount === null + ? $this->itemsPerPage + : min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage); + } +} diff --git a/vendor/nette/utils/src/Utils/Random.php b/vendor/nette/utils/src/Utils/Random.php new file mode 100644 index 0000000..5f5ee59 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Random.php @@ -0,0 +1,44 @@ + 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, + 'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, + ]; + + + public static function isBuiltinType(string $type): bool + { + return isset(self::BUILTIN_TYPES[strtolower($type)]); + } + + + public static function getReturnType(\ReflectionFunctionAbstract $func): ?string + { + return $func->hasReturnType() + ? self::normalizeType($func->getReturnType()->getName(), $func) + : null; + } + + + public static function getParameterType(\ReflectionParameter $param): ?string + { + return $param->hasType() + ? self::normalizeType($param->getType()->getName(), $param) + : null; + } + + + public static function getPropertyType(\ReflectionProperty $prop): ?string + { + return PHP_VERSION_ID >= 70400 && $prop->hasType() + ? self::normalizeType($prop->getType()->getName(), $prop) + : null; + } + + + private static function normalizeType(string $type, $reflection): string + { + $lower = strtolower($type); + if ($lower === 'self') { + return $reflection->getDeclaringClass()->getName(); + } elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) { + return $reflection->getDeclaringClass()->getParentClass()->getName(); + } else { + return $type; + } + } + + + /** + * @return mixed + * @throws \ReflectionException when default value is not available or resolvable + */ + public static function getParameterDefaultValue(\ReflectionParameter $param) + { + if ($param->isDefaultValueConstant()) { + $const = $orig = $param->getDefaultValueConstantName(); + $pair = explode('::', $const); + if (isset($pair[1])) { + if (strtolower($pair[0]) === 'self') { + $pair[0] = $param->getDeclaringClass()->getName(); + } + try { + $rcc = new \ReflectionClassConstant($pair[0], $pair[1]); + } catch (\ReflectionException $e) { + $name = self::toString($param); + throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.", 0, $e); + } + return $rcc->getValue(); + + } elseif (!defined($const)) { + $const = substr((string) strrchr($const, '\\'), 1); + if (!defined($const)) { + $name = self::toString($param); + throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name."); + } + } + return constant($const); + } + return $param->getDefaultValue(); + } + + + /** + * Returns declaring class or trait. + */ + public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass + { + foreach ($prop->getDeclaringClass()->getTraits() as $trait) { + if ($trait->hasProperty($prop->getName()) + && $trait->getProperty($prop->getName())->getDocComment() === $prop->getDocComment() + ) { + return self::getPropertyDeclaringClass($trait->getProperty($prop->getName())); + } + } + return $prop->getDeclaringClass(); + } + + + /** + * Are documentation comments available? + */ + public static function areCommentsAvailable(): bool + { + static $res; + return $res === null + ? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment() + : $res; + } + + + public static function toString(\Reflector $ref): string + { + if ($ref instanceof \ReflectionClass) { + return $ref->getName(); + } elseif ($ref instanceof \ReflectionMethod) { + return $ref->getDeclaringClass()->getName() . '::' . $ref->getName(); + } elseif ($ref instanceof \ReflectionFunction) { + return $ref->getName(); + } elseif ($ref instanceof \ReflectionProperty) { + return self::getPropertyDeclaringClass($ref)->getName() . '::$' . $ref->getName(); + } elseif ($ref instanceof \ReflectionParameter) { + return '$' . $ref->getName() . ' in ' . self::toString($ref->getDeclaringFunction()) . '()'; + } else { + throw new Nette\InvalidArgumentException; + } + } + + + /** + * Expands class name into full name. + * @throws Nette\InvalidArgumentException + */ + public static function expandClassName(string $name, \ReflectionClass $rc): string + { + $lower = strtolower($name); + if (empty($name)) { + throw new Nette\InvalidArgumentException('Class name must not be empty.'); + + } elseif (isset(self::BUILTIN_TYPES[$lower])) { + return $lower; + + } elseif ($lower === 'self') { + return $rc->getName(); + + } elseif ($name[0] === '\\') { // fully qualified name + return ltrim($name, '\\'); + } + + $uses = self::getUseStatements($rc); + $parts = explode('\\', $name, 2); + if (isset($uses[$parts[0]])) { + $parts[0] = $uses[$parts[0]]; + return implode('\\', $parts); + + } elseif ($rc->inNamespace()) { + return $rc->getNamespaceName() . '\\' . $name; + + } else { + return $name; + } + } + + + /** + * @return array of [alias => class] + */ + public static function getUseStatements(\ReflectionClass $class): array + { + if ($class->isAnonymous()) { + throw new Nette\NotImplementedException('Anonymous classes are not supported.'); + } + static $cache = []; + if (!isset($cache[$name = $class->getName()])) { + if ($class->isInternal()) { + $cache[$name] = []; + } else { + $code = file_get_contents($class->getFileName()); + $cache = self::parseUseStatements($code, $name) + $cache; + } + } + return $cache[$name]; + } + + + /** + * Parses PHP code to [class => [alias => class, ...]] + */ + private static function parseUseStatements(string $code, string $forClass = null): array + { + try { + $tokens = token_get_all($code, TOKEN_PARSE); + } catch (\ParseError $e) { + trigger_error($e->getMessage(), E_USER_NOTICE); + $tokens = []; + } + $namespace = $class = $classLevel = $level = null; + $res = $uses = []; + + while ($token = current($tokens)) { + next($tokens); + switch (is_array($token) ? $token[0] : $token) { + case T_NAMESPACE: + $namespace = ltrim(self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\'); + $uses = []; + break; + + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + if ($name = self::fetch($tokens, T_STRING)) { + $class = $namespace . $name; + $classLevel = $level + 1; + $res[$class] = $uses; + if ($class === $forClass) { + return $res; + } + } + break; + + case T_USE: + while (!$class && ($name = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]))) { + $name = ltrim($name, '\\'); + if (self::fetch($tokens, '{')) { + while ($suffix = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR])) { + if (self::fetch($tokens, T_AS)) { + $uses[self::fetch($tokens, T_STRING)] = $name . $suffix; + } else { + $tmp = explode('\\', $suffix); + $uses[end($tmp)] = $name . $suffix; + } + if (!self::fetch($tokens, ',')) { + break; + } + } + + } elseif (self::fetch($tokens, T_AS)) { + $uses[self::fetch($tokens, T_STRING)] = $name; + + } else { + $tmp = explode('\\', $name); + $uses[end($tmp)] = $name; + } + if (!self::fetch($tokens, ',')) { + break; + } + } + break; + + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': + $level++; + break; + + case '}': + if ($level === $classLevel) { + $class = $classLevel = null; + } + $level--; + } + } + + return $res; + } + + + private static function fetch(&$tokens, $take) + { + $res = null; + while ($token = current($tokens)) { + [$token, $s] = is_array($token) ? $token : [$token, $token]; + if (in_array($token, (array) $take, true)) { + $res .= $s; + } elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) { + break; + } + next($tokens); + } + return $res; + } +} diff --git a/vendor/nette/utils/src/Utils/SmartObject.php b/vendor/nette/utils/src/Utils/SmartObject.php new file mode 100644 index 0000000..d1bf1e9 --- /dev/null +++ b/vendor/nette/utils/src/Utils/SmartObject.php @@ -0,0 +1,121 @@ +$name)) { + foreach ($this->$name as $handler) { + $handler(...$args); + } + } elseif ($this->$name !== null) { + throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . gettype($this->$name) . ' given.'); + } + + } else { + ObjectHelpers::strictCall($class, $name); + } + } + + + /** + * @throws MemberAccessException + */ + public static function __callStatic(string $name, array $args) + { + ObjectHelpers::strictStaticCall(static::class, $name); + } + + + /** + * @return mixed + * @throws MemberAccessException if the property is not defined. + */ + public function &__get(string $name) + { + $class = get_class($this); + + if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter + if (!($prop & 0b0001)) { + throw new MemberAccessException("Cannot read a write-only property $class::\$$name."); + } + $m = ($prop & 0b0010 ? 'get' : 'is') . $name; + if ($prop & 0b0100) { // return by reference + return $this->$m(); + } else { + $val = $this->$m(); + return $val; + } + } else { + ObjectHelpers::strictGet($class, $name); + } + } + + + /** + * @return void + * @throws MemberAccessException if the property is not defined or is read-only + */ + public function __set(string $name, $value) + { + $class = get_class($this); + + if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property + $this->$name = $value; + + } elseif ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property setter + if (!($prop & 0b1000)) { + throw new MemberAccessException("Cannot write to a read-only property $class::\$$name."); + } + $this->{'set' . $name}($value); + + } else { + ObjectHelpers::strictSet($class, $name); + } + } + + + /** + * @return void + * @throws MemberAccessException + */ + public function __unset(string $name) + { + $class = get_class($this); + if (!ObjectHelpers::hasProperty($class, $name)) { + throw new MemberAccessException("Cannot unset the property $class::\$$name."); + } + } + + + public function __isset(string $name): bool + { + return isset(ObjectHelpers::getMagicProperties(get_class($this))[$name]); + } +} diff --git a/vendor/nette/utils/src/Utils/StaticClass.php b/vendor/nette/utils/src/Utils/StaticClass.php new file mode 100644 index 0000000..c34fa73 --- /dev/null +++ b/vendor/nette/utils/src/Utils/StaticClass.php @@ -0,0 +1,36 @@ += 0xD800 && $code <= 0xDFFF) || $code > 0x10FFFF) { + throw new Nette\InvalidArgumentException('Code point must be in range 0x0 to 0xD7FF or 0xE000 to 0x10FFFF.'); + } + return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code)); + } + + + /** + * Starts the $haystack string with the prefix $needle? + */ + public static function startsWith(string $haystack, string $needle): bool + { + return strncmp($haystack, $needle, strlen($needle)) === 0; + } + + + /** + * Ends the $haystack string with the suffix $needle? + */ + public static function endsWith(string $haystack, string $needle): bool + { + return $needle === '' || substr($haystack, -strlen($needle)) === $needle; + } + + + /** + * Does $haystack contain $needle? + */ + public static function contains(string $haystack, string $needle): bool + { + return strpos($haystack, $needle) !== false; + } + + + /** + * Returns a part of UTF-8 string. + */ + public static function substring(string $s, int $start, int $length = null): string + { + if (function_exists('mb_substr')) { + return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster + } elseif ($length === null) { + $length = self::length($s); + } elseif ($start < 0 && $length < 0) { + $start += self::length($s); // unifies iconv_substr behavior with mb_substr + } + return iconv_substr($s, $start, $length, 'UTF-8'); + } + + + /** + * Removes special controls characters and normalizes line endings, spaces and normal form to NFC in UTF-8 string. + */ + public static function normalize(string $s): string + { + // convert to compressed normal form (NFC) + if (class_exists('Normalizer', false)) { + $s = \Normalizer::normalize($s, \Normalizer::FORM_C); + } + + $s = self::normalizeNewLines($s); + + // remove control characters; leave \t + \n + $s = preg_replace('#[\x00-\x08\x0B-\x1F\x7F-\x9F]+#u', '', $s); + + // right trim + $s = preg_replace('#[\t ]+$#m', '', $s); + + // leading and trailing blank lines + $s = trim($s, "\n"); + + return $s; + } + + + /** + * Standardize line endings to unix-like. + */ + public static function normalizeNewLines(string $s): string + { + return str_replace(["\r\n", "\r"], "\n", $s); + } + + + /** + * Converts UTF-8 string to ASCII. + */ + public static function toAscii(string $s): string + { + static $transliterator = null; + if ($transliterator === null && class_exists('Transliterator', false)) { + $transliterator = \Transliterator::create('Any-Latin; Latin-ASCII'); + } + + $s = preg_replace('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s); + $s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06"); + $s = str_replace( + ["\u{201E}", "\u{201C}", "\u{201D}", "\u{201A}", "\u{2018}", "\u{2019}", "\u{B0}"], + ["\x03", "\x03", "\x03", "\x02", "\x02", "\x02", "\x04"], $s + ); + if ($transliterator !== null) { + $s = $transliterator->transliterate($s); + } + if (ICONV_IMPL === 'glibc') { + $s = str_replace( + ["\u{BB}", "\u{AB}", "\u{2026}", "\u{2122}", "\u{A9}", "\u{AE}"], + ['>>', '<<', '...', 'TM', '(c)', '(R)'], $s + ); + $s = iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s); + $s = strtr($s, "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e" + . "\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3" + . "\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8" + . "\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe" + . "\x96\xa0\x8b\x97\x9b\xa6\xad\xb7", + 'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.'); + $s = preg_replace('#[^\x00-\x7F]++#', '', $s); + } else { + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + } + $s = str_replace(['`', "'", '"', '^', '~', '?'], '', $s); + return strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?'); + } + + + /** + * Converts UTF-8 string to web safe characters [a-z0-9-] text. + */ + public static function webalize(string $s, string $charlist = null, bool $lower = true): string + { + $s = self::toAscii($s); + if ($lower) { + $s = strtolower($s); + } + $s = preg_replace('#[^a-z0-9' . ($charlist !== null ? preg_quote($charlist, '#') : '') . ']+#i', '-', $s); + $s = trim($s, '-'); + return $s; + } + + + /** + * Truncates UTF-8 string to maximal length. + */ + public static function truncate(string $s, int $maxLen, string $append = "\u{2026}"): string + { + if (self::length($s) > $maxLen) { + $maxLen -= self::length($append); + if ($maxLen < 1) { + return $append; + + } elseif ($matches = self::match($s, '#^.{1,' . $maxLen . '}(?=[\s\x00-/:-@\[-`{-~])#us')) { + return $matches[0] . $append; + + } else { + return self::substring($s, 0, $maxLen) . $append; + } + } + return $s; + } + + + /** + * Indents UTF-8 string from the left. + */ + public static function indent(string $s, int $level = 1, string $chars = "\t"): string + { + if ($level > 0) { + $s = self::replace($s, '#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level)); + } + return $s; + } + + + /** + * Converts UTF-8 string to lower case. + */ + public static function lower(string $s): string + { + return mb_strtolower($s, 'UTF-8'); + } + + + /** + * Converts first character to lower case. + */ + public static function firstLower(string $s): string + { + return self::lower(self::substring($s, 0, 1)) . self::substring($s, 1); + } + + + /** + * Converts UTF-8 string to upper case. + */ + public static function upper(string $s): string + { + return mb_strtoupper($s, 'UTF-8'); + } + + + /** + * Converts first character to upper case. + */ + public static function firstUpper(string $s): string + { + return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1); + } + + + /** + * Capitalizes UTF-8 string. + */ + public static function capitalize(string $s): string + { + return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8'); + } + + + /** + * Case-insensitive compares UTF-8 strings. + */ + public static function compare(string $left, string $right, int $len = null): bool + { + if (class_exists('Normalizer', false)) { + $left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster + $right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster + } + + if ($len < 0) { + $left = self::substring($left, $len, -$len); + $right = self::substring($right, $len, -$len); + } elseif ($len !== null) { + $left = self::substring($left, 0, $len); + $right = self::substring($right, 0, $len); + } + return self::lower($left) === self::lower($right); + } + + + /** + * Finds the length of common prefix of strings. + * @param string[] $strings + */ + public static function findPrefix(array $strings): string + { + $first = array_shift($strings); + for ($i = 0; $i < strlen($first); $i++) { + foreach ($strings as $s) { + if (!isset($s[$i]) || $first[$i] !== $s[$i]) { + while ($i && $first[$i - 1] >= "\x80" && $first[$i] >= "\x80" && $first[$i] < "\xC0") { + $i--; + } + return substr($first, 0, $i); + } + } + } + return $first; + } + + + /** + * Returns number of characters (not bytes) in UTF-8 string. + * That is the number of Unicode code points which may differ from the number of graphemes. + */ + public static function length(string $s): int + { + return function_exists('mb_strlen') ? mb_strlen($s, 'UTF-8') : strlen(utf8_decode($s)); + } + + + /** + * Strips whitespace from UTF-8 string. + */ + public static function trim(string $s, string $charlist = self::TRIM_CHARACTERS): string + { + $charlist = preg_quote($charlist, '#'); + return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', ''); + } + + + /** + * Pad a UTF-8 string to a certain length with another string. + */ + public static function padLeft(string $s, int $length, string $pad = ' '): string + { + $length = max(0, $length - self::length($s)); + $padLen = self::length($pad); + return str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen) . $s; + } + + + /** + * Pad a UTF-8 string to a certain length with another string. + */ + public static function padRight(string $s, int $length, string $pad = ' '): string + { + $length = max(0, $length - self::length($s)); + $padLen = self::length($pad); + return $s . str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen); + } + + + /** + * Reverse string. + */ + public static function reverse(string $s): string + { + return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s))); + } + + + /** + * Returns part of $haystack before $nth occurence of $needle (negative value means searching from the end). + * @return string|null returns null if the needle was not found + */ + public static function before(string $haystack, string $needle, int $nth = 1): ?string + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : substr($haystack, 0, $pos); + } + + + /** + * Returns part of $haystack after $nth occurence of $needle (negative value means searching from the end). + * @return string|null returns null if the needle was not found + */ + public static function after(string $haystack, string $needle, int $nth = 1): ?string + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : substr($haystack, $pos + strlen($needle)); + } + + + /** + * Returns position of $nth occurence of $needle in $haystack (negative value means searching from the end). + * @return int|null offset in characters or null if the needle was not found + */ + public static function indexOf(string $haystack, string $needle, int $nth = 1): ?int + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : self::length(substr($haystack, 0, $pos)); + } + + + /** + * Returns position of $nth occurence of $needle in $haystack. + * @return int|null offset in bytes or null if the needle was not found + */ + private static function pos(string $haystack, string $needle, int $nth = 1): ?int + { + if (!$nth) { + return null; + } elseif ($nth > 0) { + if ($needle === '') { + return 0; + } + $pos = 0; + while (($pos = strpos($haystack, $needle, $pos)) !== false && --$nth) { + $pos++; + } + } else { + $len = strlen($haystack); + if ($needle === '') { + return $len; + } + $pos = $len - 1; + while (($pos = strrpos($haystack, $needle, $pos - $len)) !== false && ++$nth) { + $pos--; + } + } + return $pos === false ? null : $pos; + } + + + /** + * Splits string by a regular expression. + */ + public static function split(string $subject, string $pattern, int $flags = 0): array + { + return self::pcre('preg_split', [$pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE]); + } + + + /** + * Performs a regular expression match. Accepts flag PREG_OFFSET_CAPTURE (returned in bytes). + */ + public static function match(string $subject, string $pattern, int $flags = 0, int $offset = 0): ?array + { + if ($offset > strlen($subject)) { + return null; + } + return self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset]) + ? $m + : null; + } + + + /** + * Performs a global regular expression match. Accepts flag PREG_OFFSET_CAPTURE (returned in bytes), PREG_SET_ORDER is default. + */ + public static function matchAll(string $subject, string $pattern, int $flags = 0, int $offset = 0): array + { + if ($offset > strlen($subject)) { + return []; + } + self::pcre('preg_match_all', [ + $pattern, $subject, &$m, + ($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER), + $offset, + ]); + return $m; + } + + + /** + * Perform a regular expression search and replace. + * @param string|array $pattern + * @param string|callable $replacement + */ + public static function replace(string $subject, $pattern, $replacement = null, int $limit = -1): string + { + if (is_object($replacement) || is_array($replacement)) { + if (!is_callable($replacement, false, $textual)) { + throw new Nette\InvalidStateException("Callback '$textual' is not callable."); + } + return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit]); + + } elseif ($replacement === null && is_array($pattern)) { + $replacement = array_values($pattern); + $pattern = array_keys($pattern); + } + + return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]); + } + + + /** @internal */ + public static function pcre(string $func, array $args) + { + $res = Callback::invokeSafe($func, $args, function (string $message) use ($args): void { + // compile-time error, not detectable by preg_last_error + throw new RegexpException($message . ' in pattern: ' . implode(' or ', (array) $args[0])); + }); + + if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars + && ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true)) + ) { + throw new RegexpException((RegexpException::MESSAGES[$code] ?? 'Unknown error') + . ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code); + } + return $res; + } +} diff --git a/vendor/nette/utils/src/Utils/Validators.php b/vendor/nette/utils/src/Utils/Validators.php new file mode 100644 index 0000000..7de1c04 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Validators.php @@ -0,0 +1,344 @@ + 'is_array', + 'bool' => 'is_bool', + 'boolean' => 'is_bool', + 'float' => 'is_float', + 'int' => 'is_int', + 'integer' => 'is_int', + 'null' => 'is_null', + 'object' => 'is_object', + 'resource' => 'is_resource', + 'scalar' => 'is_scalar', + 'string' => 'is_string', + + // pseudo-types + 'callable' => [__CLASS__, 'isCallable'], + 'iterable' => 'is_iterable', + 'list' => [Arrays::class, 'isList'], + 'mixed' => [__CLASS__, 'isMixed'], + 'none' => [__CLASS__, 'isNone'], + 'number' => [__CLASS__, 'isNumber'], + 'numeric' => [__CLASS__, 'isNumeric'], + 'numericint' => [__CLASS__, 'isNumericInt'], + + // string patterns + 'alnum' => 'ctype_alnum', + 'alpha' => 'ctype_alpha', + 'digit' => 'ctype_digit', + 'lower' => 'ctype_lower', + 'pattern' => null, + 'space' => 'ctype_space', + 'unicode' => [__CLASS__, 'isUnicode'], + 'upper' => 'ctype_upper', + 'xdigit' => 'ctype_xdigit', + + // syntax validation + 'email' => [__CLASS__, 'isEmail'], + 'identifier' => [__CLASS__, 'isPhpIdentifier'], + 'uri' => [__CLASS__, 'isUri'], + 'url' => [__CLASS__, 'isUrl'], + + // environment validation + 'class' => 'class_exists', + 'interface' => 'interface_exists', + 'directory' => 'is_dir', + 'file' => 'is_file', + 'type' => [__CLASS__, 'isType'], + ]; + + protected static $counters = [ + 'string' => 'strlen', + 'unicode' => [Strings::class, 'length'], + 'array' => 'count', + 'list' => 'count', + 'alnum' => 'strlen', + 'alpha' => 'strlen', + 'digit' => 'strlen', + 'lower' => 'strlen', + 'space' => 'strlen', + 'upper' => 'strlen', + 'xdigit' => 'strlen', + ]; + + + /** + * Throws exception if a variable is of unexpected type (separated by pipe). + */ + public static function assert($value, string $expected, string $label = 'variable'): void + { + if (!static::is($value, $expected)) { + $expected = str_replace(['|', ':'], [' or ', ' in range '], $expected); + static $translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null']; + $type = $translate[gettype($value)] ?? gettype($value); + if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) { + $type .= ' ' . var_export($value, true); + } elseif (is_object($value)) { + $type .= ' ' . get_class($value); + } + throw new AssertionException("The $label expects to be $expected, $type given."); + } + } + + + /** + * Throws exception if an array field is missing or of unexpected type (separated by pipe). + */ + public static function assertField(array $arr, $field, string $expected = null, string $label = "item '%' in array"): void + { + if (!array_key_exists($field, $arr)) { + throw new AssertionException('Missing ' . str_replace('%', $field, $label) . '.'); + + } elseif ($expected) { + static::assert($arr[$field], $expected, str_replace('%', $field, $label)); + } + } + + + /** + * Finds whether a variable is of expected type (separated by pipe). + */ + public static function is($value, string $expected): bool + { + foreach (explode('|', $expected) as $item) { + if (substr($item, -2) === '[]') { + if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) { + return true; + } + continue; + } elseif (substr($item, 0, 1) === '?') { + $item = substr($item, 1); + if ($value === null) { + return true; + } + } + + [$type] = $item = explode(':', $item, 2); + if (isset(static::$validators[$type])) { + try { + if (!static::$validators[$type]($value)) { + continue; + } + } catch (\TypeError $e) { + continue; + } + } elseif ($type === 'pattern') { + if (preg_match('|^' . ($item[1] ?? '') . '$|D', $value)) { + return true; + } + continue; + } elseif (!$value instanceof $type) { + continue; + } + + if (isset($item[1])) { + $length = $value; + if (isset(static::$counters[$type])) { + $length = static::$counters[$type]($value); + } + $range = explode('..', $item[1]); + if (!isset($range[1])) { + $range[1] = $range[0]; + } + if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) { + continue; + } + } + return true; + } + return false; + } + + + /** + * Finds whether all values are of expected type (separated by pipe). + */ + public static function everyIs(iterable $values, string $expected): bool + { + foreach ($values as $value) { + if (!static::is($value, $expected)) { + return false; + } + } + return true; + } + + + /** + * Finds whether a value is an integer or a float. + */ + public static function isNumber($value): bool + { + return is_int($value) || is_float($value); + } + + + /** + * Finds whether a value is an integer. + */ + public static function isNumericInt($value): bool + { + return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value)); + } + + + /** + * Finds whether a string is a floating point number in decimal base. + */ + public static function isNumeric($value): bool + { + return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]*[.]?[0-9]+$#D', $value)); + } + + + /** + * Finds whether a value is a syntactically correct callback. + */ + public static function isCallable($value): bool + { + return $value && is_callable($value, true); + } + + + /** + * Finds whether a value is an UTF-8 encoded string. + */ + public static function isUnicode($value): bool + { + return is_string($value) && preg_match('##u', $value); + } + + + /** + * Finds whether a value is "falsy". + */ + public static function isNone($value): bool + { + return $value == null; // intentionally == + } + + + /** @internal */ + public static function isMixed(): bool + { + return true; + } + + + /** + * Finds whether a variable is a zero-based integer indexed array. + */ + public static function isList($value): bool + { + return Arrays::isList($value); + } + + + /** + * Is a value in specified min and max value pair? + */ + public static function isInRange($value, array $range): bool + { + if ($value === null || !(isset($range[0]) || isset($range[1]))) { + return false; + } + $limit = $range[0] ?? $range[1]; + if (is_string($limit)) { + $value = (string) $value; + } elseif ($limit instanceof \DateTimeInterface) { + if (!$value instanceof \DateTimeInterface) { + return false; + } + } elseif (is_numeric($value)) { + $value *= 1; + } else { + return false; + } + return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1])); + } + + + /** + * Finds whether a string is a valid email address. + */ + public static function isEmail(string $value): bool + { + $atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part + $alpha = "a-z\x80-\xFF"; // superset of IDN + return (bool) preg_match("(^ + (\"([ !#-[\\]-~]*|\\\\[ -~])+\"|$atom+(\\.$atom+)*) # quoted or unquoted + @ + ([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034 + [$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain + $)Dix", $value); + } + + + /** + * Finds whether a string is a valid http(s) URL. + */ + public static function isUrl(string $value): bool + { + $alpha = "a-z\x80-\xFF"; + return (bool) preg_match("(^ + https?://( + (([-_0-9$alpha]+\\.)* # subdomain + [0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)? # domain + [$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain + |\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4 + |\[[0-9a-f:]{3,39}\] # IPv6 + )(:\\d{1,5})? # port + (/\\S*)? # path + (\?\\S*)? # query + (\#\\S*)? # fragment + $)Dix", $value); + } + + + /** + * Finds whether a string is a valid URI according to RFC 1738. + */ + public static function isUri(string $value): bool + { + return (bool) preg_match('#^[a-z\d+\.-]+:\S+$#Di', $value); + } + + + /** + * Checks whether the input is a class, interface or trait. + */ + public static function isType(string $type): bool + { + return class_exists($type) || interface_exists($type) || trait_exists($type); + } + + + /** + * Checks whether the input is a valid PHP identifier. + */ + public static function isPhpIdentifier(string $value): bool + { + return is_string($value) && preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $value); + } +} diff --git a/vendor/nette/utils/src/Utils/exceptions.php b/vendor/nette/utils/src/Utils/exceptions.php new file mode 100644 index 0000000..08ffec7 --- /dev/null +++ b/vendor/nette/utils/src/Utils/exceptions.php @@ -0,0 +1,159 @@ + 'Internal error', + PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted', + PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted', + PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data', + PREG_BAD_UTF8_OFFSET_ERROR => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point', + 6 => 'Failed due to limited JIT stack space', // PREG_JIT_STACKLIMIT_ERROR + ]; +} + + +/** + * The exception that indicates assertion error. + */ +class AssertionException extends \Exception +{ +} diff --git a/vendor/opis/closure/CHANGELOG.md b/vendor/opis/closure/CHANGELOG.md new file mode 100644 index 0000000..44370bf --- /dev/null +++ b/vendor/opis/closure/CHANGELOG.md @@ -0,0 +1,220 @@ +CHANGELOG +--------- + +### v3.5.1, 2019.11.30 + +- Bugfix. See #47 + +### v3.5.0, 2019.11.29 + +- Added support for short closures (arrow functions) +- Added `isShortClosure` method to `Opis\Closure\ReflectionClosure` + +### v3.4.2, 2019.11.29 + +- Added `stream_set_option()` + +### v3.4.1, 2019.10.19 + +- Fixed a [bug](https://github.com/opis/closure/issues/40) that prevented serialization to work correctly. + +### v3.4.0, 2019.09.03 + +- Added `createClosure` static method in `Opis\Closure\SerializableClosure`. +This method creates a new closure from arbitrary code, emulating `create_function`, +but without using eval + +### v3.3.1, 2019.07.10 + +- Use `sha1` instead of `md5` for hashing file names in `Opis\Closure\ReflectionClosure` class + +### v3.3.0, 2019.05.31 + +- Fixed a bug that prevented signed closures to properly work when the serialized string +contains invalid UTF-8 chars. Starting with this version `json_encode` is no longer used +when signing a closure. Backward compatibility is maintained and all closures that were +previously signed using the old method will continue to work. + +### v3.2.0, 2019.05.05 + +- Since an unsigned closure can be unserialized when no security provider is set, +there is no reason to treat differently a signed closure in the same situation. +Therefore, the `Opis\Closure\SecurityException` exception is no longer thrown when +unserializing a signed closure, if no security provider is set. + +### v3.1.6, 2019.02.22 + +- Fixed a bug that occurred when trying to set properties of classes that were not defined in user-land. +Those properties are now ignored. + +### v3.1.5, 2019.01.14 + +- Improved parser + +### v3.1.4, 2019.01.14 + +- Added support for static methods that are named using PHP keywords or magic constants. +Ex: `A::new()`, `A::use()`, `A::if()`, `A::function()`, `A::__DIR__()`, etc. +- Used `@internal` to mark classes & methods that are for internal use only and +backward compatibility is not guaranteed. + +### v3.1.3, 2019.01.07 + +- Fixed a bug that prevented traits to be correctly resolved when used by an +anonymous class +- Fixed a bug that occurred when `$this` keyword was used inside an anonymous class + +### v3.1.2, 2018.12.16 + +* Fixed a bug regarding comma trail in group-use statements. See [issue 23](https://github.com/opis/closure/issues/23) + +### v3.1.1, 2018.10.02 + +* Fixed a bug where `parent` keyword was treated like a class-name and scope was not added to the +serialized closure +* Fixed a bug where return type was not properly handled for nested closures +* Support for anonymous classes was improved + +### v3.1.0, 2018.09.20 + +* Added `transformUseVariables` and `resolveUseVariables` to +`Opis\Closure\SerializableClosure` class. +* Added `removeSecurityProvider` static method to +`Opis\Closure\SerializableClosure` class. +* Fixed some security related issues where a user was able to unserialize an unsigned +closure, even when a security provider was in use. + +### v3.0.12, 2018.02.23 + +* Bugfix. See [issue 20](https://github.com/opis/closure/issues/20) + +### v3.0.11, 2018.01.22 + +* Bugfix. See [issue 18](https://github.com/opis/closure/issues/18) + +### v3.0.10, 2018.01.04 + +* Improved support for PHP 7.1 & 7.2 + +### v3.0.9, 2018.01.04 + +* Fixed a bug where the return type was not properly resolved. +See [issue 17](https://github.com/opis/closure/issues/17) +* Added more tests + +### v3.0.8, 2017.12.18 + +* Fixed a bug. See [issue 16](https://github.com/opis/closure/issues/16) + +### v3.0.7, 2017.10.31 + +* Bugfix: static properties are ignored now, since they are not serializable + +### v3.0.6, 2017.10.06 + +* Fixed a bug introduced by accident in 3.0.5 + +### v3.0.5, 2017.09.18 + +* Fixed a bug related to nested references + +### v3.0.4, 2017.09.18 + +* \[*internal*\] Refactored `SerializableClosure::mapPointers` method +* \[*internal*\] Added a new optional argument to `SerializableClosure::unwrapClosures` +* \[*internal*\] Removed `SerializableClosure::getClosurePointer` method +* Fixed various bugs + +### v3.0.3, 2017.09.06 + +* Fixed a bug related to nested object references +* \[*internal*\] `Opis\Closure\ClosureScope` now extends `SplObjectStorage` +* \[*internal*\] The `storage` property was removed from `Opis\Closure\ClosureScope` +* \[*internal*\] The `instances` and `objects` properties were removed from `Opis\Closure\ClosureContext` + +### v3.0.2, 2017.08.28 + +* Fixed a bug where `$this` object was not handled properly inside the +`SerializableClosre::serialize` method. + +### v3.0.1, 2017.04.13 + +* Fixed a bug in 'ignore_next' state + +### v3.0.0, 2017.04.07 + +* Dropped PHP 5.3 support +* Moved source files from `lib` to `src` folder +* Removed second parameter from `Opis\Closure\SerializableClosure::from` method and from constructor +* Removed `Opis\Closure\{SecurityProviderInterface, DefaultSecurityProvider, SecureClosure}` classes +* Refactored how signed closures were handled +* Added `wrapClosures` and `unwrapClosures` static methods to `Opis\Closure\SerializableClosure` class +* Added `Opis\Colosure\serialize` and `Opis\Closure\unserialize` functions +* Improved serialization. You can now serialize arbitrary objects and the library will automatically wrap all closures + +### v2.4.0, 2016.12.16 + +* The parser was refactored and improved +* Refactored `Opis\Closure\SerializableClosure::__invoke` method +* `Opis\Closure\{ISecurityProvider, SecurityProvider}` were added +* `Opis\Closure\{SecurityProviderInterface, DefaultSecurityProvider, SecureClosure}` were deprecated +and they will be removed in the next major version +* `setSecretKey` and `addSecurityProvider` static methods were added to `Opis\Closure\SerializableClosure` + +### v2.3.2, 2016.12.15 + +* Fixed a bug that prevented namespace resolution to be done properly + +### v2.3.1, 2016.12.13 + +* Hotfix. See [PR](https://github.com/opis/closure/pull/7) + +### v2.3.0, 2016.11.17 + +* Added `isBindingRequired` and `isScopeRequired` to the `Opis\Closure\ReflectionClosure` class +* Automatically detects when the scope and/or the bound object of a closure needs to be serialized. + +### v2.2.1, 2016.08.20 + +* Fixed a bug in `Opis\Closure\ReflectionClosure::fetchItems` + +### v2.2.0, 2016.07.26 + +* Fixed CS +* `Opis\Closure\ClosureContext`, `Opis\Closure\ClosureScope`, `Opis\Closure\SelfReference` + and `Opis\Closure\SecurityException` classes were moved into separate files +* Added support for PHP7 syntax +* Fixed some bugs in `Opis\Closure\ReflectionClosure` class +* Improved closure parser +* Added an analyzer for SuperClosure library + +### v2.1.0, 2015.09.30 + +* Added support for the missing `__METHOD__`, `__FUNCTION__` and `__TRAIT__` magic constants +* Added some security related classes and interfaces: `Opis\Closure\SecurityProviderInterface`, +`Opis\Closure\DefaultSecurityProvider`, `Opis\Closure\SecureClosure`, `Opis\Closure\SecurityException`. +* Fiexed a bug in `Opis\Closure\ReflectionClosure::getClasses` method +* Other minor bugfixes +* Added support for static closures +* Added public `isStatic` method to `Opis\Closure\ReflectionClosure` class + + +### v2.0.1, 2015.09.23 + +* Removed `branch-alias` property from `composer.json` +* Bugfix. See [issue #6](https://github.com/opis/closure/issues/6) + +### v2.0.0, 2015.07.31 + +* The closure parser was improved +* Class names are now automatically resolved +* Added support for the `#trackme` directive which allows tracking closure's residing source + +### v1.3.0, 2014.10.18 + +* Added autoload file +* Changed README file + +### Opis Closure 1.2.2 + +* Started changelog diff --git a/vendor/opis/closure/LICENSE b/vendor/opis/closure/LICENSE new file mode 100644 index 0000000..9c0a19b --- /dev/null +++ b/vendor/opis/closure/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018-2019 Zindex Software + +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/opis/closure/NOTICE b/vendor/opis/closure/NOTICE new file mode 100644 index 0000000..ae5caa6 --- /dev/null +++ b/vendor/opis/closure/NOTICE @@ -0,0 +1,9 @@ +Opis Closure +Copyright 2018-2019 Zindex Software + +This product includes software developed at +Zindex Software (http://zindex.software). + +This software was originally developed by Marius Sarca and Sorin Sarca +(Copyright 2014-2018). The copyright info was changed with the permission +of the original authors. \ No newline at end of file diff --git a/vendor/opis/closure/README.md b/vendor/opis/closure/README.md new file mode 100644 index 0000000..b98d68d --- /dev/null +++ b/vendor/opis/closure/README.md @@ -0,0 +1,98 @@ +Opis Closure +==================== +[![Build Status](https://travis-ci.org/opis/closure.png)](https://travis-ci.org/opis/closure) +[![Latest Stable Version](https://poser.pugx.org/opis/closure/v/stable.png)](https://packagist.org/packages/opis/closure) +[![Latest Unstable Version](https://poser.pugx.org/opis/closure/v/unstable.png)](https://packagist.org/packages/opis/closure) +[![License](https://poser.pugx.org/opis/closure/license.png)](https://packagist.org/packages/opis/closure) + +Serializable closures +--------------------- +**Opis Closure** is a library that aims to overcome PHP's limitations regarding closure +serialization by providing a wrapper that will make all closures serializable. + +**The library's key features:** + +- Serialize any closure +- Serialize arbitrary objects +- Doesn't use `eval` for closure serialization or unserialization +- Works with any PHP version that has support for closures +- Supports PHP 7 syntax +- Handles all variables referenced/imported in `use()` and automatically wraps all referenced/imported closures for +proper serialization +- Handles recursive closures +- Handles magic constants like `__FILE__`, `__DIR__`, `__LINE__`, `__NAMESPACE__`, `__CLASS__`, +`__TRAIT__`, `__METHOD__` and `__FUNCTION__`. +- Automatically resolves all class names, function names and constant names used inside the closure +- Track closure's residing source by using the `#trackme` directive +- Simple and very fast parser +- Any error or exception, that might occur when executing an unserialized closure, can be caught and treated properly +- You can serialize/unserialize any closure unlimited times, even those previously unserialized +(this is possible because `eval()` is not used for unserialization) +- Handles static closures +- Supports cryptographically signed closures +- Provides a reflector that can give you information about the serialized closure +- Provides an analyzer for *SuperClosure* library +- Automatically detects when the scope and/or the bound object of a closure needs to be serialized +in order for the closure to work after deserialization + +### Documentation + +The full documentation for this library can be found [here][documentation]. + +### License + +**Opis Closure** is licensed under the [MIT License (MIT)][license]. + +### Requirements + +* PHP ^5.4 || ^7.0 + +## Installation + +**Opis Closure** is available on [Packagist] and it can be installed from a +command line interface by using [Composer]. + +```bash +composer require opis/closure +``` + +Or you could directly reference it into your `composer.json` file as a dependency + +```json +{ + "require": { + "opis/closure": "^3.5" + } +} +``` + +### Migrating from 2.x + +If your project needs to support PHP 5.3 you can continue using the `2.x` version +of **Opis Closure**. Otherwise, assuming you are not using one of the removed/refactored classes or features(see +[CHANGELOG]), migrating to version `3.x` is simply a matter of updating your `composer.json` file. + +### Semantic versioning + +**Opis Closure** follows [semantic versioning][SemVer] specifications. + +### Arbitrary object serialization + +This feature was primarily introduced in order to support serializing an object bound +to a closure and available via `$this`. The implementation is far from being perfect +and it's really hard to make it work flawless. I will try to improve this, but I can +not guarantee anything. So my advice regarding the `Opis\Closure\serialize|unserialize` +functions is to use them with caution. + +### SuperClosure support + +**Opis Closure** is shipped with an analyzer(`Opis\Closure\Analyzer`) which +aims to provide *Opis Closure*'s parsing precision and speed to [SuperClosure]. + +[documentation]: https://www.opis.io/closure "Opis Closure" +[license]: http://opensource.org/licenses/MIT "MIT License" +[Packagist]: https://packagist.org/packages/opis/closure "Packagist" +[Composer]: https://getcomposer.org "Composer" +[SuperClosure]: https://github.com/jeremeamia/super_closure "SuperClosure" +[SemVer]: http://semver.org/ "Semantic versioning" +[CHANGELOG]: https://github.com/opis/closure/blob/master/CHANGELOG.md "Changelog" \ No newline at end of file diff --git a/vendor/opis/closure/autoload.php b/vendor/opis/closure/autoload.php new file mode 100644 index 0000000..a928014 --- /dev/null +++ b/vendor/opis/closure/autoload.php @@ -0,0 +1,39 @@ +getClosureScopeClass(); + + $data = [ + 'reflection' => $reflection, + 'code' => $reflection->getCode(), + 'hasThis' => $reflection->isBindingRequired(), + 'context' => $reflection->getUseVariables(), + 'hasRefs' => false, + 'binding' => $reflection->getClosureThis(), + 'scope' => $scope ? $scope->getName() : null, + 'isStatic' => $reflection->isStatic(), + ]; + + return $data; + } + + /** + * @param array $data + * @return mixed + */ + protected function determineCode(array &$data) + { + return null; + } + + /** + * @param array $data + * @return mixed + */ + protected function determineContext(array &$data) + { + return null; + } + +} diff --git a/vendor/opis/closure/src/ClosureContext.php b/vendor/opis/closure/src/ClosureContext.php new file mode 100644 index 0000000..d68cf98 --- /dev/null +++ b/vendor/opis/closure/src/ClosureContext.php @@ -0,0 +1,34 @@ +scope = new ClosureScope(); + $this->locks = 0; + } +} \ No newline at end of file diff --git a/vendor/opis/closure/src/ClosureScope.php b/vendor/opis/closure/src/ClosureScope.php new file mode 100644 index 0000000..71ad414 --- /dev/null +++ b/vendor/opis/closure/src/ClosureScope.php @@ -0,0 +1,25 @@ +content = "length = strlen($this->content); + return true; + } + + public function stream_read($count) + { + $value = substr($this->content, $this->pointer, $count); + $this->pointer += $count; + return $value; + } + + public function stream_eof() + { + return $this->pointer >= $this->length; + } + + public function stream_set_option($option, $arg1, $arg2) + { + return false; + } + + public function stream_stat() + { + $stat = stat(__FILE__); + $stat[7] = $stat['size'] = $this->length; + return $stat; + } + + public function url_stat($path, $flags) + { + $stat = stat(__FILE__); + $stat[7] = $stat['size'] = $this->length; + return $stat; + } + + public function stream_seek($offset, $whence = SEEK_SET) + { + $crt = $this->pointer; + + switch ($whence) { + case SEEK_SET: + $this->pointer = $offset; + break; + case SEEK_CUR: + $this->pointer += $offset; + break; + case SEEK_END: + $this->pointer = $this->length + $offset; + break; + } + + if ($this->pointer < 0 || $this->pointer >= $this->length) { + $this->pointer = $crt; + return false; + } + + return true; + } + + public function stream_tell() + { + return $this->pointer; + } + + public static function register() + { + if (!static::$isRegistered) { + static::$isRegistered = stream_wrapper_register(static::STREAM_PROTO, __CLASS__); + } + } + +} diff --git a/vendor/opis/closure/src/ISecurityProvider.php b/vendor/opis/closure/src/ISecurityProvider.php new file mode 100644 index 0000000..d3b0a29 --- /dev/null +++ b/vendor/opis/closure/src/ISecurityProvider.php @@ -0,0 +1,25 @@ +code = $code; + parent::__construct($closure); + } + + /** + * @return bool + */ + public function isStatic() + { + if ($this->isStaticClosure === null) { + $this->isStaticClosure = strtolower(substr($this->getCode(), 0, 6)) === 'static'; + } + + return $this->isStaticClosure; + } + + public function isShortClosure() + { + if ($this->isShortClosure === null) { + $code = $this->getCode(); + if ($this->isStatic()) { + $code = substr($code, 6); + } + $this->isShortClosure = strtolower(substr(trim($code), 0, 2)) === 'fn'; + } + + return $this->isShortClosure; + } + + /** + * @return string + */ + public function getCode() + { + if($this->code !== null){ + return $this->code; + } + + $fileName = $this->getFileName(); + $line = $this->getStartLine() - 1; + + $match = ClosureStream::STREAM_PROTO . '://'; + + if ($line === 1 && substr($fileName, 0, strlen($match)) === $match) { + return $this->code = substr($fileName, strlen($match)); + } + + $className = null; + $fn = false; + + + if (null !== $className = $this->getClosureScopeClass()) { + $className = '\\' . trim($className->getName(), '\\'); + } + + + if($php7 = PHP_MAJOR_VERSION === 7){ + switch (PHP_MINOR_VERSION){ + case 0: + $php7_types = array('string', 'int', 'bool', 'float'); + break; + case 1: + $php7_types = array('string', 'int', 'bool', 'float', 'void'); + break; + case 2: + default: + $php7_types = array('string', 'int', 'bool', 'float', 'void', 'object'); + } + $fn = PHP_MINOR_VERSION === 4; + } + + $ns = $this->getNamespaceName(); + $nsf = $ns == '' ? '' : ($ns[0] == '\\' ? $ns : '\\' . $ns); + + $_file = var_export($fileName, true); + $_dir = var_export(dirname($fileName), true); + $_namespace = var_export($ns, true); + $_class = var_export(trim($className, '\\'), true); + $_function = $ns . ($ns == '' ? '' : '\\') . '{closure}'; + $_method = ($className == '' ? '' : trim($className, '\\') . '::') . $_function; + $_function = var_export($_function, true); + $_method = var_export($_method, true); + $_trait = null; + + $tokens = $this->getTokens(); + $state = $lastState = 'start'; + $inside_anonymous = false; + $isShortClosure = false; + $anonymous_mark = 0; + $open = 0; + $code = ''; + $id_start = $id_start_ci = $id_name = $context = ''; + $classes = $functions = $constants = null; + $use = array(); + $lineAdd = 0; + $isUsingScope = false; + $isUsingThisObject = false; + + for($i = 0, $l = count($tokens); $i < $l; $i++) { + $token = $tokens[$i]; + switch ($state) { + case 'start': + if ($token[0] === T_FUNCTION || $token[0] === T_STATIC) { + $code .= $token[1]; + $state = $token[0] === T_FUNCTION ? 'function' : 'static'; + } elseif ($fn && $token[0] === T_FN) { + $isShortClosure = true; + $code .= $token[1]; + $state = 'closure_args'; + } + break; + case 'static': + if ($token[0] === T_WHITESPACE || $token[0] === T_COMMENT || $token[0] === T_FUNCTION) { + $code .= $token[1]; + if ($token[0] === T_FUNCTION) { + $state = 'function'; + } + } elseif ($fn && $token[0] === T_FN) { + $isShortClosure = true; + $code .= $token[1]; + $state = 'closure_args'; + } else { + $code = ''; + $state = 'start'; + } + break; + case 'function': + switch ($token[0]){ + case T_STRING: + $code = ''; + $state = 'named_function'; + break; + case '(': + $code .= '('; + $state = 'closure_args'; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + } + break; + case 'named_function': + if($token[0] === T_FUNCTION || $token[0] === T_STATIC){ + $code = $token[1]; + $state = $token[0] === T_FUNCTION ? 'function' : 'static'; + } elseif ($fn && $token[0] === T_FN) { + $isShortClosure = true; + $code .= $token[1]; + $state = 'closure_args'; + } + break; + case 'closure_args': + switch ($token[0]){ + case T_NS_SEPARATOR: + case T_STRING: + $id_start = $token[1]; + $id_start_ci = strtolower($id_start); + $id_name = ''; + $context = 'args'; + $state = 'id_name'; + $lastState = 'closure_args'; + break; + case T_USE: + $code .= $token[1]; + $state = 'use'; + break; + case T_DOUBLE_ARROW: + $code .= $token[1]; + if ($isShortClosure) { + $state = 'closure'; + } + break; + case '=': + $code .= $token; + $lastState = 'closure_args'; + $state = 'ignore_next'; + break; + case ':': + $code .= ':'; + $state = 'return'; + break; + case '{': + $code .= '{'; + $state = 'closure'; + $open++; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + } + break; + case 'use': + switch ($token[0]){ + case T_VARIABLE: + $use[] = substr($token[1], 1); + $code .= $token[1]; + break; + case '{': + $code .= '{'; + $state = 'closure'; + $open++; + break; + case ':': + $code .= ':'; + $state = 'return'; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + break; + } + break; + case 'return': + switch ($token[0]){ + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + $code .= $token[1]; + break; + case T_NS_SEPARATOR: + case T_STRING: + $id_start = $token[1]; + $id_start_ci = strtolower($id_start); + $id_name = ''; + $context = 'return_type'; + $state = 'id_name'; + $lastState = 'return'; + break 2; + case T_DOUBLE_ARROW: + $code .= $token[1]; + if ($isShortClosure) { + $state = 'closure'; + } + break; + case '{': + $code .= '{'; + $state = 'closure'; + $open++; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + break; + } + break; + case 'closure': + switch ($token[0]){ + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case T_STRING_VARNAME: + case '{': + $code .= '{'; + $open++; + break; + case '}': + $code .= '}'; + if(--$open === 0 && !$isShortClosure){ + break 3; + } elseif ($inside_anonymous) { + $inside_anonymous = !($open === $anonymous_mark); + } + break; + case '(': + case '[': + $code .= $token[0]; + if ($isShortClosure) { + $open++; + } + break; + case ')': + case ']': + if ($isShortClosure) { + if ($open === 0) { + break 3; + } + --$open; + } + $code .= $token[0]; + break; + case ',': + case ';': + if ($isShortClosure && $open === 0) { + break 3; + } + $code .= $token[0]; + break; + case T_LINE: + $code .= $token[2] - $line + $lineAdd; + break; + case T_FILE: + $code .= $_file; + break; + case T_DIR: + $code .= $_dir; + break; + case T_NS_C: + $code .= $_namespace; + break; + case T_CLASS_C: + $code .= $_class; + break; + case T_FUNC_C: + $code .= $_function; + break; + case T_METHOD_C: + $code .= $_method; + break; + case T_COMMENT: + if (substr($token[1], 0, 8) === '#trackme') { + $timestamp = time(); + $code .= '/**' . PHP_EOL; + $code .= '* Date : ' . date(DATE_W3C, $timestamp) . PHP_EOL; + $code .= '* Timestamp : ' . $timestamp . PHP_EOL; + $code .= '* Line : ' . ($line + 1) . PHP_EOL; + $code .= '* File : ' . $_file . PHP_EOL . '*/' . PHP_EOL; + $lineAdd += 5; + } else { + $code .= $token[1]; + } + break; + case T_VARIABLE: + if($token[1] == '$this' && !$inside_anonymous){ + $isUsingThisObject = true; + } + $code .= $token[1]; + break; + case T_STATIC: + $isUsingScope = true; + $code .= $token[1]; + break; + case T_NS_SEPARATOR: + case T_STRING: + $id_start = $token[1]; + $id_start_ci = strtolower($id_start); + $id_name = ''; + $context = 'root'; + $state = 'id_name'; + $lastState = 'closure'; + break 2; + case T_NEW: + $code .= $token[1]; + $context = 'new'; + $state = 'id_start'; + $lastState = 'closure'; + break 2; + case T_USE: + $code .= $token[1]; + $context = 'use'; + $state = 'id_start'; + $lastState = 'closure'; + break; + case T_INSTANCEOF: + $code .= $token[1]; + $context = 'instanceof'; + $state = 'id_start'; + $lastState = 'closure'; + break; + case T_OBJECT_OPERATOR: + case T_DOUBLE_COLON: + $code .= $token[1]; + $lastState = 'closure'; + $state = 'ignore_next'; + break; + case T_FUNCTION: + $code .= $token[1]; + $state = 'closure_args'; + break; + case T_TRAIT_C: + if ($_trait === null) { + $startLine = $this->getStartLine(); + $endLine = $this->getEndLine(); + $structures = $this->getStructures(); + + $_trait = ''; + + foreach ($structures as &$struct) { + if ($struct['type'] === 'trait' && + $struct['start'] <= $startLine && + $struct['end'] >= $endLine + ) { + $_trait = ($ns == '' ? '' : $ns . '\\') . $struct['name']; + break; + } + } + + $_trait = var_export($_trait, true); + } + + $code .= $_trait; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + } + break; + case 'ignore_next': + switch ($token[0]){ + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + $code .= $token[1]; + break; + case T_CLASS: + case T_NEW: + case T_STATIC: + case T_VARIABLE: + case T_STRING: + case T_CLASS_C: + case T_FILE: + case T_DIR: + case T_METHOD_C: + case T_FUNC_C: + case T_FUNCTION: + case T_INSTANCEOF: + case T_LINE: + case T_NS_C: + case T_TRAIT_C: + case T_USE: + $code .= $token[1]; + $state = $lastState; + break; + default: + $state = $lastState; + $i--; + } + break; + case 'id_start': + switch ($token[0]){ + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + $code .= $token[1]; + break; + case T_NS_SEPARATOR: + case T_STRING: + case T_STATIC: + $id_start = $token[1]; + $id_start_ci = strtolower($id_start); + $id_name = ''; + $state = 'id_name'; + break 2; + case T_VARIABLE: + $code .= $token[1]; + $state = $lastState; + break; + case T_CLASS: + $code .= $token[1]; + $state = 'anonymous'; + break; + default: + $i--;//reprocess last + $state = 'id_name'; + } + break; + case 'id_name': + switch ($token[0]){ + case T_NS_SEPARATOR: + case T_STRING: + $id_name .= $token[1]; + break; + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + $id_name .= $token[1]; + break; + case '(': + if ($isShortClosure) { + $open++; + } + if($context === 'new' || false !== strpos($id_name, '\\')){ + if($id_start !== '\\'){ + if ($classes === null) { + $classes = $this->getClasses(); + } + if (isset($classes[$id_start_ci])) { + $id_start = $classes[$id_start_ci]; + } + if($id_start[0] !== '\\'){ + $id_start = $nsf . '\\' . $id_start; + } + } + } else { + if($id_start !== '\\'){ + if($functions === null){ + $functions = $this->getFunctions(); + } + if(isset($functions[$id_start_ci])){ + $id_start = $functions[$id_start_ci]; + } + } + } + $code .= $id_start . $id_name . '('; + $state = $lastState; + break; + case T_VARIABLE: + case T_DOUBLE_COLON: + if($id_start !== '\\') { + if($id_start_ci === 'self' || $id_start_ci === 'static' || $id_start_ci === 'parent'){ + $isUsingScope = true; + } elseif (!($php7 && in_array($id_start_ci, $php7_types))){ + if ($classes === null) { + $classes = $this->getClasses(); + } + if (isset($classes[$id_start_ci])) { + $id_start = $classes[$id_start_ci]; + } + if($id_start[0] !== '\\'){ + $id_start = $nsf . '\\' . $id_start; + } + } + } + $code .= $id_start . $id_name . $token[1]; + $state = $token[0] === T_DOUBLE_COLON ? 'ignore_next' : $lastState; + break; + default: + if($id_start !== '\\'){ + if($context === 'use' || + $context === 'instanceof' || + $context === 'args' || + $context === 'return_type' || + $context === 'extends' + ){ + if($id_start_ci === 'self' || $id_start_ci === 'static' || $id_start_ci === 'parent'){ + $isUsingScope = true; + } elseif (!($php7 && in_array($id_start_ci, $php7_types))){ + if($classes === null){ + $classes = $this->getClasses(); + } + if(isset($classes[$id_start_ci])){ + $id_start = $classes[$id_start_ci]; + } + if($id_start[0] !== '\\'){ + $id_start = $nsf . '\\' . $id_start; + } + } + } else { + if($constants === null){ + $constants = $this->getConstants(); + } + if(isset($constants[$id_start])){ + $id_start = $constants[$id_start]; + } + } + } + $code .= $id_start . $id_name; + $state = $lastState; + $i--;//reprocess last token + } + break; + case 'anonymous': + switch ($token[0]) { + case T_NS_SEPARATOR: + case T_STRING: + $id_start = $token[1]; + $id_start_ci = strtolower($id_start); + $id_name = ''; + $state = 'id_name'; + $context = 'extends'; + $lastState = 'anonymous'; + break; + case '{': + $state = 'closure'; + if (!$inside_anonymous) { + $inside_anonymous = true; + $anonymous_mark = $open; + } + $i--; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + } + break; + } + } + + if ($isShortClosure) { + $code .= ';'; + $this->useVariables = $this->getStaticVariables(); + } else { + $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use)); + } + + $this->isShortClosure = $isShortClosure; + $this->isBindingRequired = $isUsingThisObject; + $this->isScopeRequired = $isUsingScope; + $this->code = $code; + + return $this->code; + } + + /** + * @return array + */ + public function getUseVariables() + { + if($this->useVariables !== null){ + return $this->useVariables; + } + + $tokens = $this->getTokens(); + $use = array(); + $state = 'start'; + + foreach ($tokens as &$token) { + $is_array = is_array($token); + + switch ($state) { + case 'start': + if ($is_array && $token[0] === T_USE) { + $state = 'use'; + } + break; + case 'use': + if ($is_array) { + if ($token[0] === T_VARIABLE) { + $use[] = substr($token[1], 1); + } + } elseif ($token == ')') { + break 2; + } + break; + } + } + + $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use)); + + return $this->useVariables; + } + + /** + * return bool + */ + public function isBindingRequired() + { + if($this->isBindingRequired === null){ + $this->getCode(); + } + + return $this->isBindingRequired; + } + + /** + * return bool + */ + public function isScopeRequired() + { + if($this->isScopeRequired === null){ + $this->getCode(); + } + + return $this->isScopeRequired; + } + + /** + * @return string + */ + protected function getHashedFileName() + { + if ($this->hashedName === null) { + $this->hashedName = sha1($this->getFileName()); + } + + return $this->hashedName; + } + + /** + * @return array + */ + protected function getFileTokens() + { + $key = $this->getHashedFileName(); + + if (!isset(static::$files[$key])) { + static::$files[$key] = token_get_all(file_get_contents($this->getFileName())); + } + + return static::$files[$key]; + } + + /** + * @return array + */ + protected function getTokens() + { + if ($this->tokens === null) { + $tokens = $this->getFileTokens(); + $startLine = $this->getStartLine(); + $endLine = $this->getEndLine(); + $results = array(); + $start = false; + + foreach ($tokens as &$token) { + if (!is_array($token)) { + if ($start) { + $results[] = $token; + } + + continue; + } + + $line = $token[2]; + + if ($line <= $endLine) { + if ($line >= $startLine) { + $start = true; + $results[] = $token; + } + + continue; + } + + break; + } + + $this->tokens = $results; + } + + return $this->tokens; + } + + /** + * @return array + */ + protected function getClasses() + { + $key = $this->getHashedFileName(); + + if (!isset(static::$classes[$key])) { + $this->fetchItems(); + } + + return static::$classes[$key]; + } + + /** + * @return array + */ + protected function getFunctions() + { + $key = $this->getHashedFileName(); + + if (!isset(static::$functions[$key])) { + $this->fetchItems(); + } + + return static::$functions[$key]; + } + + /** + * @return array + */ + protected function getConstants() + { + $key = $this->getHashedFileName(); + + if (!isset(static::$constants[$key])) { + $this->fetchItems(); + } + + return static::$constants[$key]; + } + + /** + * @return array + */ + protected function getStructures() + { + $key = $this->getHashedFileName(); + + if (!isset(static::$structures[$key])) { + $this->fetchItems(); + } + + return static::$structures[$key]; + } + + protected function fetchItems() + { + $key = $this->getHashedFileName(); + + $classes = array(); + $functions = array(); + $constants = array(); + $structures = array(); + $tokens = $this->getFileTokens(); + + $open = 0; + $state = 'start'; + $lastState = ''; + $prefix = ''; + $name = ''; + $alias = ''; + $isFunc = $isConst = false; + + $startLine = $endLine = 0; + $structType = $structName = ''; + $structIgnore = false; + + foreach ($tokens as $token) { + + switch ($state) { + case 'start': + switch ($token[0]) { + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + $state = 'before_structure'; + $startLine = $token[2]; + $structType = $token[0] == T_CLASS + ? 'class' + : ($token[0] == T_INTERFACE ? 'interface' : 'trait'); + break; + case T_USE: + $state = 'use'; + $prefix = $name = $alias = ''; + $isFunc = $isConst = false; + break; + case T_FUNCTION: + $state = 'structure'; + $structIgnore = true; + break; + case T_NEW: + $state = 'new'; + break; + case T_OBJECT_OPERATOR: + case T_DOUBLE_COLON: + $state = 'invoke'; + break; + } + break; + case 'use': + switch ($token[0]) { + case T_FUNCTION: + $isFunc = true; + break; + case T_CONST: + $isConst = true; + break; + case T_NS_SEPARATOR: + $name .= $token[1]; + break; + case T_STRING: + $name .= $token[1]; + $alias = $token[1]; + break; + case T_AS: + $lastState = 'use'; + $state = 'alias'; + break; + case '{': + $prefix = $name; + $name = $alias = ''; + $state = 'use-group'; + break; + case ',': + case ';': + if ($name === '' || $name[0] !== '\\') { + $name = '\\' . $name; + } + + if ($alias !== '') { + if ($isFunc) { + $functions[strtolower($alias)] = $name; + } elseif ($isConst) { + $constants[$alias] = $name; + } else { + $classes[strtolower($alias)] = $name; + } + } + $name = $alias = ''; + $state = $token === ';' ? 'start' : 'use'; + break; + } + break; + case 'use-group': + switch ($token[0]) { + case T_NS_SEPARATOR: + $name .= $token[1]; + break; + case T_STRING: + $name .= $token[1]; + $alias = $token[1]; + break; + case T_AS: + $lastState = 'use-group'; + $state = 'alias'; + break; + case ',': + case '}': + + if ($prefix === '' || $prefix[0] !== '\\') { + $prefix = '\\' . $prefix; + } + + if ($alias !== '') { + if ($isFunc) { + $functions[strtolower($alias)] = $prefix . $name; + } elseif ($isConst) { + $constants[$alias] = $prefix . $name; + } else { + $classes[strtolower($alias)] = $prefix . $name; + } + } + $name = $alias = ''; + $state = $token === '}' ? 'use' : 'use-group'; + break; + } + break; + case 'alias': + if ($token[0] === T_STRING) { + $alias = $token[1]; + $state = $lastState; + } + break; + case 'new': + switch ($token[0]) { + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + break 2; + case T_CLASS: + $state = 'structure'; + $structIgnore = true; + break; + default: + $state = 'start'; + } + break; + case 'invoke': + switch ($token[0]) { + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + break 2; + default: + $state = 'start'; + } + break; + case 'before_structure': + if ($token[0] == T_STRING) { + $structName = $token[1]; + $state = 'structure'; + } + break; + case 'structure': + switch ($token[0]) { + case '{': + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case T_STRING_VARNAME: + $open++; + break; + case '}': + if (--$open == 0) { + if(!$structIgnore){ + $structures[] = array( + 'type' => $structType, + 'name' => $structName, + 'start' => $startLine, + 'end' => $endLine, + ); + } + $structIgnore = false; + $state = 'start'; + } + break; + default: + if (is_array($token)) { + $endLine = $token[2]; + } + } + break; + } + } + + static::$classes[$key] = $classes; + static::$functions[$key] = $functions; + static::$constants[$key] = $constants; + static::$structures[$key] = $structures; + } +} diff --git a/vendor/opis/closure/src/SecurityException.php b/vendor/opis/closure/src/SecurityException.php new file mode 100644 index 0000000..6a107ee --- /dev/null +++ b/vendor/opis/closure/src/SecurityException.php @@ -0,0 +1,18 @@ +secret = $secret; + } + + /** + * @inheritdoc + */ + public function sign($closure) + { + return array( + 'closure' => $closure, + 'hash' => base64_encode(hash_hmac('sha256', $closure, $this->secret, true)), + ); + } + + /** + * @inheritdoc + */ + public function verify(array $data) + { + return base64_encode(hash_hmac('sha256', $data['closure'], $this->secret, true)) === $data['hash']; + } +} \ No newline at end of file diff --git a/vendor/opis/closure/src/SelfReference.php b/vendor/opis/closure/src/SelfReference.php new file mode 100644 index 0000000..3c6ec80 --- /dev/null +++ b/vendor/opis/closure/src/SelfReference.php @@ -0,0 +1,31 @@ +hash = $hash; + } +} \ No newline at end of file diff --git a/vendor/opis/closure/src/SerializableClosure.php b/vendor/opis/closure/src/SerializableClosure.php new file mode 100644 index 0000000..c4991cf --- /dev/null +++ b/vendor/opis/closure/src/SerializableClosure.php @@ -0,0 +1,668 @@ +closure = $closure; + if (static::$context !== null) { + $this->scope = static::$context->scope; + $this->scope->toserialize++; + } + } + + /** + * Get the Closure object + * + * @return Closure The wrapped closure + */ + public function getClosure() + { + return $this->closure; + } + + /** + * Get the reflector for closure + * + * @return ReflectionClosure + */ + public function getReflector() + { + if ($this->reflector === null) { + $this->reflector = new ReflectionClosure($this->closure, $this->code); + $this->code = null; + } + + return $this->reflector; + } + + /** + * Implementation of magic method __invoke() + */ + public function __invoke() + { + return call_user_func_array($this->closure, func_get_args()); + } + + /** + * Implementation of Serializable::serialize() + * + * @return string The serialized closure + */ + public function serialize() + { + if ($this->scope === null) { + $this->scope = new ClosureScope(); + $this->scope->toserialize++; + } + + $this->scope->serializations++; + + $scope = $object = null; + $reflector = $this->getReflector(); + + if($reflector->isBindingRequired()){ + $object = $reflector->getClosureThis(); + static::wrapClosures($object, $this->scope); + if($scope = $reflector->getClosureScopeClass()){ + $scope = $scope->name; + } + } elseif($reflector->isScopeRequired()) { + if($scope = $reflector->getClosureScopeClass()){ + $scope = $scope->name; + } + } + + $this->reference = spl_object_hash($this->closure); + + $this->scope[$this->closure] = $this; + + $use = $this->transformUseVariables($reflector->getUseVariables()); + $code = $reflector->getCode(); + + $this->mapByReference($use); + + $ret = \serialize(array( + 'use' => $use, + 'function' => $code, + 'scope' => $scope, + 'this' => $object, + 'self' => $this->reference, + )); + + if (static::$securityProvider !== null) { + $data = static::$securityProvider->sign($ret); + $ret = '@' . $data['hash'] . '.' . $data['closure']; + } + + if (!--$this->scope->serializations && !--$this->scope->toserialize) { + $this->scope = null; + } + + return $ret; + } + + /** + * Transform the use variables before serialization. + * + * @param array $data The Closure's use variables + * @return array + */ + protected function transformUseVariables($data) + { + return $data; + } + + /** + * Implementation of Serializable::unserialize() + * + * @param string $data Serialized data + * @throws SecurityException + */ + public function unserialize($data) + { + ClosureStream::register(); + + if (static::$securityProvider !== null) { + if ($data[0] !== '@') { + throw new SecurityException("The serialized closure is not signed. ". + "Make sure you use a security provider for both serialization and unserialization."); + } + + if ($data[1] !== '{') { + $separator = strpos($data, '.'); + if ($separator === false) { + throw new SecurityException('Invalid signed closure'); + } + $hash = substr($data, 1, $separator - 1); + $closure = substr($data, $separator + 1); + + $data = ['hash' => $hash, 'closure' => $closure]; + + unset($hash, $closure); + } else { + $data = json_decode(substr($data, 1), true); + } + + if (!is_array($data) || !static::$securityProvider->verify($data)) { + throw new SecurityException("Your serialized closure might have been modified and it's unsafe to be unserialized. " . + "Make sure you use the same security provider, with the same settings, " . + "both for serialization and unserialization."); + } + + $data = $data['closure']; + } elseif ($data[0] === '@') { + if ($data[1] !== '{') { + $separator = strpos($data, '.'); + if ($separator === false) { + throw new SecurityException('Invalid signed closure'); + } + $hash = substr($data, 1, $separator - 1); + $closure = substr($data, $separator + 1); + + $data = ['hash' => $hash, 'closure' => $closure]; + + unset($hash, $closure); + } else { + $data = json_decode(substr($data, 1), true); + } + + if (!is_array($data) || !isset($data['closure']) || !isset($data['hash'])) { + throw new SecurityException('Invalid signed closure'); + } + + $data = $data['closure']; + } + + $this->code = \unserialize($data); + + // unset data + unset($data); + + $this->code['objects'] = array(); + + if ($this->code['use']) { + $this->scope = new ClosureScope(); + $this->code['use'] = $this->resolveUseVariables($this->code['use']); + $this->mapPointers($this->code['use']); + extract($this->code['use'], EXTR_OVERWRITE | EXTR_REFS); + $this->scope = null; + } + + $this->closure = include(ClosureStream::STREAM_PROTO . '://' . $this->code['function']); + + if($this->code['this'] === $this){ + $this->code['this'] = null; + } + + if ($this->code['scope'] !== null || $this->code['this'] !== null) { + $this->closure = $this->closure->bindTo($this->code['this'], $this->code['scope']); + } + + if(!empty($this->code['objects'])){ + foreach ($this->code['objects'] as $item){ + $item['property']->setValue($item['instance'], $item['object']->getClosure()); + } + } + + $this->code = $this->code['function']; + } + + /** + * Resolve the use variables after unserialization. + * + * @param array $data The Closure's transformed use variables + * @return array + */ + protected function resolveUseVariables($data) + { + return $data; + } + + /** + * Wraps a closure and sets the serialization context (if any) + * + * @param Closure $closure Closure to be wrapped + * + * @return self The wrapped closure + */ + public static function from(Closure $closure) + { + if (static::$context === null) { + $instance = new static($closure); + } elseif (isset(static::$context->scope[$closure])) { + $instance = static::$context->scope[$closure]; + } else { + $instance = new static($closure); + static::$context->scope[$closure] = $instance; + } + + return $instance; + } + + /** + * Increments the context lock counter or creates a new context if none exist + */ + public static function enterContext() + { + if (static::$context === null) { + static::$context = new ClosureContext(); + } + + static::$context->locks++; + } + + /** + * Decrements the context lock counter and destroy the context when it reaches to 0 + */ + public static function exitContext() + { + if (static::$context !== null && !--static::$context->locks) { + static::$context = null; + } + } + + /** + * @param string $secret + */ + public static function setSecretKey($secret) + { + if(static::$securityProvider === null){ + static::$securityProvider = new SecurityProvider($secret); + } + } + + /** + * @param ISecurityProvider $securityProvider + */ + public static function addSecurityProvider(ISecurityProvider $securityProvider) + { + static::$securityProvider = $securityProvider; + } + + /** + * Remove security provider + */ + public static function removeSecurityProvider() + { + static::$securityProvider = null; + } + + /** + * @return null|ISecurityProvider + */ + public static function getSecurityProvider() + { + return static::$securityProvider; + } + + /** + * Wrap closures + * + * @internal + * @param $data + * @param ClosureScope|SplObjectStorage|null $storage + */ + public static function wrapClosures(&$data, SplObjectStorage $storage = null) + { + if($storage === null){ + $storage = static::$context->scope; + } + + if($data instanceof Closure){ + $data = static::from($data); + } elseif (is_array($data)){ + if(isset($data[self::ARRAY_RECURSIVE_KEY])){ + return; + } + $data[self::ARRAY_RECURSIVE_KEY] = true; + foreach ($data as $key => &$value){ + if($key === self::ARRAY_RECURSIVE_KEY){ + continue; + } + static::wrapClosures($value, $storage); + } + unset($value); + unset($data[self::ARRAY_RECURSIVE_KEY]); + } elseif($data instanceof \stdClass){ + if(isset($storage[$data])){ + $data = $storage[$data]; + return; + } + $data = $storage[$data] = clone($data); + foreach ($data as &$value){ + static::wrapClosures($value, $storage); + } + unset($value); + } elseif (is_object($data) && ! $data instanceof static){ + if(isset($storage[$data])){ + $data = $storage[$data]; + return; + } + $instance = $data; + $reflection = new ReflectionObject($instance); + if(!$reflection->isUserDefined()){ + $storage[$instance] = $data; + return; + } + $storage[$instance] = $data = $reflection->newInstanceWithoutConstructor(); + + do{ + if(!$reflection->isUserDefined()){ + break; + } + foreach ($reflection->getProperties() as $property){ + if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){ + continue; + } + $property->setAccessible(true); + $value = $property->getValue($instance); + if(is_array($value) || is_object($value)){ + static::wrapClosures($value, $storage); + } + $property->setValue($data, $value); + }; + } while($reflection = $reflection->getParentClass()); + } + } + + /** + * Unwrap closures + * + * @internal + * @param $data + * @param SplObjectStorage|null $storage + */ + public static function unwrapClosures(&$data, SplObjectStorage $storage = null) + { + if($storage === null){ + $storage = static::$context->scope; + } + + if($data instanceof static){ + $data = $data->getClosure(); + } elseif (is_array($data)){ + if(isset($data[self::ARRAY_RECURSIVE_KEY])){ + return; + } + $data[self::ARRAY_RECURSIVE_KEY] = true; + foreach ($data as $key => &$value){ + if($key === self::ARRAY_RECURSIVE_KEY){ + continue; + } + static::unwrapClosures($value, $storage); + } + unset($data[self::ARRAY_RECURSIVE_KEY]); + }elseif ($data instanceof \stdClass){ + if(isset($storage[$data])){ + return; + } + $storage[$data] = true; + foreach ($data as &$property){ + static::unwrapClosures($property, $storage); + } + } elseif (is_object($data) && !($data instanceof Closure)){ + if(isset($storage[$data])){ + return; + } + $storage[$data] = true; + $reflection = new ReflectionObject($data); + + do{ + if(!$reflection->isUserDefined()){ + break; + } + foreach ($reflection->getProperties() as $property){ + if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){ + continue; + } + $property->setAccessible(true); + $value = $property->getValue($data); + if(is_array($value) || is_object($value)){ + static::unwrapClosures($value, $storage); + $property->setValue($data, $value); + } + }; + } while($reflection = $reflection->getParentClass()); + } + } + + /** + * Creates a new closure from arbitrary code, + * emulating create_function, but without using eval + * + * @param string$args + * @param string $code + * @return Closure + */ + public static function createClosure($args, $code) + { + ClosureStream::register(); + return include(ClosureStream::STREAM_PROTO . '://function(' . $args. '){' . $code . '};'); + } + + /** + * Internal method used to map closure pointers + * @internal + * @param $data + */ + protected function mapPointers(&$data) + { + $scope = $this->scope; + + if ($data instanceof static) { + $data = &$data->closure; + } elseif (is_array($data)) { + if(isset($data[self::ARRAY_RECURSIVE_KEY])){ + return; + } + $data[self::ARRAY_RECURSIVE_KEY] = true; + foreach ($data as $key => &$value){ + if($key === self::ARRAY_RECURSIVE_KEY){ + continue; + } elseif ($value instanceof static) { + $data[$key] = &$value->closure; + } elseif ($value instanceof SelfReference && $value->hash === $this->code['self']){ + $data[$key] = &$this->closure; + } else { + $this->mapPointers($value); + } + } + unset($value); + unset($data[self::ARRAY_RECURSIVE_KEY]); + } elseif ($data instanceof \stdClass) { + if(isset($scope[$data])){ + return; + } + $scope[$data] = true; + foreach ($data as $key => &$value){ + if ($value instanceof SelfReference && $value->hash === $this->code['self']){ + $data->{$key} = &$this->closure; + } elseif(is_array($value) || is_object($value)) { + $this->mapPointers($value); + } + } + unset($value); + } elseif (is_object($data) && !($data instanceof Closure)){ + if(isset($scope[$data])){ + return; + } + $scope[$data] = true; + $reflection = new ReflectionObject($data); + do{ + if(!$reflection->isUserDefined()){ + break; + } + foreach ($reflection->getProperties() as $property){ + if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){ + continue; + } + $property->setAccessible(true); + $item = $property->getValue($data); + if ($item instanceof SerializableClosure || ($item instanceof SelfReference && $item->hash === $this->code['self'])) { + $this->code['objects'][] = array( + 'instance' => $data, + 'property' => $property, + 'object' => $item instanceof SelfReference ? $this : $item, + ); + } elseif (is_array($item) || is_object($item)) { + $this->mapPointers($item); + $property->setValue($data, $item); + } + } + } while($reflection = $reflection->getParentClass()); + } + } + + /** + * Internal method used to map closures by reference + * + * @internal + * @param mixed &$data + */ + protected function mapByReference(&$data) + { + if ($data instanceof Closure) { + if($data === $this->closure){ + $data = new SelfReference($this->reference); + return; + } + + if (isset($this->scope[$data])) { + $data = $this->scope[$data]; + return; + } + + $instance = new static($data); + + if (static::$context !== null) { + static::$context->scope->toserialize--; + } else { + $instance->scope = $this->scope; + } + + $data = $this->scope[$data] = $instance; + } elseif (is_array($data)) { + if(isset($data[self::ARRAY_RECURSIVE_KEY])){ + return; + } + $data[self::ARRAY_RECURSIVE_KEY] = true; + foreach ($data as $key => &$value){ + if($key === self::ARRAY_RECURSIVE_KEY){ + continue; + } + $this->mapByReference($value); + } + unset($value); + unset($data[self::ARRAY_RECURSIVE_KEY]); + } elseif ($data instanceof \stdClass) { + if(isset($this->scope[$data])){ + $data = $this->scope[$data]; + return; + } + $instance = $data; + $this->scope[$instance] = $data = clone($data); + + foreach ($data as &$value){ + $this->mapByReference($value); + } + unset($value); + } elseif (is_object($data) && !$data instanceof SerializableClosure){ + if(isset($this->scope[$data])){ + $data = $this->scope[$data]; + return; + } + + $instance = $data; + $reflection = new ReflectionObject($data); + if(!$reflection->isUserDefined()){ + $this->scope[$instance] = $data; + return; + } + $this->scope[$instance] = $data = $reflection->newInstanceWithoutConstructor(); + + do{ + if(!$reflection->isUserDefined()){ + break; + } + foreach ($reflection->getProperties() as $property){ + if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){ + continue; + } + $property->setAccessible(true); + $value = $property->getValue($instance); + if(is_array($value) || is_object($value)){ + $this->mapByReference($value); + } + $property->setValue($data, $value); + } + } while($reflection = $reflection->getParentClass()); + } + } + +} 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..1fa8de9 --- /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/predis/predis/CHANGELOG.md b/vendor/predis/predis/CHANGELOG.md new file mode 100644 index 0000000..68d0978 --- /dev/null +++ b/vendor/predis/predis/CHANGELOG.md @@ -0,0 +1,985 @@ +v1.1.1 (2016-06-16) +================================================================================ + +- __FIX__: `password` and `database` from the global `parameters` client option + were still being applied to sentinels connections making them fail (sentinels + do not understand the `AUTH` and `SELECT` commands) (PR #346). + +- __FIX__: when a sentinel instance reports no sentinel for a service, invoking + `connect()` on the redis-sentinel connection backend should fall back to the + master connection instead of failing (ISSUE #342). + +- __FIX__: the two connection backends based on ext-phpiredis has some kind of + issues with the GC and the internal use of closures as reader callbacks that + prevented connections going out of scope from being properly collected and the + underlying stream or socket resources from being closed and freed. This should + not have had any actual effect in real-world scenarios due to the lifecycle of + PHP scripts, but we fixed it anyway (ISSUE #345). + + +v1.1.0 (2016-06-02) +================================================================================ + +- The default server profile for the client now targets Redis 3.2. + +- Responses to the following commands are not casted into booleans anymore, the + original integer value is returned: `SETNX`, `MSETNX`, `SMOVE`, `SISMEMBER`, + `HSET`, `HSETNX`, `HEXISTS`, `PFADD`, `EXISTS`, `MOVE`, `PERSIST`, `EXPIRE`, + `EXPIREAT`, `RENAMENX`. This change does not have a significant impact unless + when using strict comparisons (=== and !==) the returned value. + +- Non-boolean string values passed to the `persistent` connection parameter can + be used to create different persistent connections. Note that this feature was + already present in Predis but required both `persistent` and `path` to be set + as illustrated by [#139](https://github.com/nrk/predis/pull/139). This change + is needed to prevent confusion with how `path` is used to select a database + when using the `redis` scheme. + +- The client throws exceptions when Redis returns any kind of error response to + initialization commands (the ones being automatically sent when a connection + is established, such as `SELECT` and `AUTH` when database and password are set + in connection parameters) regardless of the value of the exception option. + +- Using `unix:///path/to/socket` in URI strings to specify a UNIX domain socket + file is now deprecated in favor of the format `unix:/path/to/socket` (note the + lack of the double slash after the scheme) and will not be supported starting + with the next major release. + +- Implemented full support for redis-sentinel. + +- Implemented the ability to specify default connection parameters for aggregate + connections with the new `parameters` client option. These parameters augment + the usual user-supplied connection parameters (but do not take the precedence + over them) when creating new connections and they are mostly useful when the + client is using aggregate connections such as redis-cluster and redis-sentinel + as these backends can create new connections on the fly based on responses and + redirections from Redis. + +- Redis servers protected by SSL-encrypted connections can be accessed by using + the `tls` or `rediss` scheme in connection parameters along with SSL-specific + options in the `ssl` parameter (see http://php.net/manual/context.ssl.php). + +- `Predis\Client` implements `IteratorAggregate` making it possible to iterate + over traversable aggregate connections and get a new client instance for each + Redis node. + +- Iterating over an instance of `Predis\Connection\Aggregate\RedisCluster` will + return all the connections mapped in the slots map instead of just the ones in + the pool. This change makes it possible, when the slots map is retrieved from + Redis, to iterate over all of the master nodes in the cluster. When the use of + `CLUSTER SLOTS` is disabled via the `useClusterSlots()` method, the iteration + returns only the connections with slots ranges associated in their parameters + or the ones initialized by `-MOVED` responses in order to make the behaviour + of the iteration consistent between the two modes of operation. + +- Various improvements to `Predis\Connection\Aggregate\MasterSlaveReplication` + (the "basic" replication backend, not the new one based on redis-sentinel): + + - When the client is not able to send a read-only command to a slave because + the current connection fails or the slave is resyncing (`-LOADING` response + returned by Redis), the backend discards the failed connection and performs + a new attempt on the next slave. When no other slave is available the master + server is used for read-only commands as last resort. + + - It is possible to discover the current replication configuration on the fly + by invoking the `discover()` method which internally relies on the output of + the command `INFO REPLICATION` executed against the master server or one of + the slaves. The backend can also be configured to do this automatically when + it fails to reach one of the servers. + + - Implemented the `switchToMaster()` and `switchToSlave()` methods to make it + easier to force a switch to the master server or a random slave when needed. + + +v1.0.4 (2016-05-30) +================================================================================ + +- Added new profile for Redis 3.2 with its new commands: `HSTRLEN`, `BITFIELD`, + `GEOADD`, `GEOHASH`, `GEOPOS`, `GEODIST`, `GEORADIUS`, `GEORADIUSBYMEMBER`. + The default server profile for Predis is still the one for Redis 3.0 you must + set the `profile` client option to `3.2` when initializing the client in order + to be able to use them when connecting to Redis 3.2. + +- Various improvements in the handling of redis-cluster: + + - If the connection to a specific node fails when executing a command, the + client tries to connect to another node in order to refresh the slots map + and perform a new attempt to execute the command. + + - Connections to nodes can be preassigned to non-contiguous slot ranges via + the `slots` parameter using a comma separator. This is how it looks like + in practice: `tcp://127.0.0.1:6379?slots=0-5460,5500-5600,11000`. + +- __FIX__: broken values returned by `Predis\Collection\Iterator\HashKey` when + iterating hash keys containing integer fields (PR #330, ISSUE #331). + +- __FIX__: prevent failures when `Predis\Connection\StreamConnection` serializes + commands with holes in their arguments (e.g. `[0 => 'key:0', 2 => 'key:2']`). + The same fix has been applied to `Predis\Protocol\Text\RequestSerializer`. + (ISSUE #316). + + +v1.0.3 (2015-07-30) +================================================================================ + +- __FIX__: the previous release introduced a severe regression on HHVM that made + the library unable to connect to Redis when using IPv4 addresses. Code running + on the standard PHP interpreter is not affected. + + +v1.0.2 (2015-07-30) +================================================================================ + +- IPv6 is now fully supported. + +- Added `redis` as an accepted scheme for connection parameters. When using this + scheme, the rules used to parse URI strings match the provisional registration + [published by IANA](http://www.iana.org/assignments/uri-schemes/prov/redis). + +- Added new or missing commands: `HSTRLEN` (>= 3.2), `ZREVRANGEBYLEX` (>= 2.8) + and `MIGRATE` (>= 2.6). + +- Implemented support for the `ZADD` modifiers `NX|XX`, `CH`, `INCR` (Redis >= + 3.0.2) using the simplified signature where scores and members are passed as + a named array. + +- __FIX__: `Predis\Configuration\Options` must not trigger the autoloader when + option values are strings (ISSUE #257). + +- __FIX__: `BITPOS` was not defined in the key-prefix processor (ISSUE #265) and + in the replication strategy. + + +v1.0.1 (2015-01-02) +================================================================================ + +- Added `BITPOS` to the server profile for Redis 2.8. + +- Connection timeout for read/write operations can now be set for UNIX sockets + where the underlying connection uses PHP's stream. + +- __FIX__: broken values returned by `Predis\Collection\Iterator\SortedSetKey` + when iterating sorted set containing integer members (ISSUE #216). + +- __FIX__: applied a minor workaround for a bug in old versions of PHP < 5.3.9 + affecting inheritance. + +- __FIX__: prevent E_NOTICE warnings when using INFO [section] returns an empty + response due to an unsupported specific set of information requested to Redis. + + +v1.0.0 (2014-08-01) +================================================================================ + +- Switched to PSR-4 for autoloading. + +- The default server profile for Redis is `3.0`. + +- Removed server profile for Redis 1.2. + +- Added `SENTINEL` to the profile for Redis 2.6 and `PUBSUB` to the profile for + Redis 2.8. + +- `Predis\Client` can now send raw commands using `Predis\Client::executeRaw()`. + +- Status responses are returned as instances of `Predis\Response\Status`, for + example +OK is not returned as boolean TRUE anymore which is a breaking change + for those using strict comparisons. Status responses can be casted to string + values carrying the original payload, so one can do `$response == 'OK'` which + is also more akin to how Redis replies to clients. + +- Commands `ZRANGE`, `ZRANGEBYSCORE`, `ZREVRANGE` and `ZREVRANGEBYSCORE` using + `WITHSCORE` return a named array of member => score instead of using an array + of [member, score] elements. Insertion order is preserved anyway due to how + PHP works internally. + +- The command `ZSCAN` returns a named array of member => score instead of using + an array of [member, score] elements. Insertion order is preserved anyway due + to how PHP works internally. + +- The rules for redis-cluster are now leveraged for empty key tags when using + client-side sharding, which means that when one or the first occurrence of {} + is found in a key it will most likely produce a different hash than previous + versions of Predis thus leading to a different partitioning in these cases. + +- Invoking `Predis\Client::connect()` when the underlying connection has been + already established does not throw any exception anymore, now the connection + simply does not attempt to perform any operation. + +- Added the `aggregate` client option, useful to fully customize how the client + should aggregate multiple connections when an array of connection parameters + is passed to `Predis\Client::__construct()`. + +- Dropped support for streamable multibulk responses. Actually we still ship the + iterator response classes just in case anyone would want to build custom stuff + at a level lower than the client abstraction (our standard and composable text + protocol processors still handle them and can be used as an example). + +- Simplified the implementation of connection parameters by removing method used + to cast to int / bool / float certain parameters supplied by users. Casting + values, if deemed necessary, should be done by the consumer or you can just + subclass `Predis\Connection\Parameters` and override the `filter()` method. + +- Changed a couple of options for our transaction abstraction: + + - `exceptions`: overrides the value of the client option with the same name. + Please note that it does not affect all the transaction control commands + such as `MULTI`, `EXEC`, `DISCARD`, `WATCH` and `UNWATCH`. + - `on_retry`: this option has been removed. + +- Removed pipeline executors, now command pipelines can be easily customized by + extending the standard `Predis\Pipeline\Pipeline` class. Accepted options when + creating a pipeline using `Predis\Client::pipeline()` are: + + - `atomic`: returns a pipeline wrapped in a MULTI / EXEC transaction + (class: `Predis\Pipeline\Atomic`). + - `fire-and-forget`: returns a pipeline that does not read back responses + (class: `Predis\Pipeline\FireAndForget`). + +- Renamed the two base abstract command classes: + + - `Predis\Command\AbstractCommand` is now `Predis\Command\Command` + - `Predis\Command\ScriptedCommand` is now `Predis\Command\ScriptCommand` + +- Dropped `Predis\Command\Command::__toString()` (see issue #151). + +- The key prefixing logic has been moved from command classes to the key prefix + processor. Developers can define or override handlers used to prefix keys, but + they can also define the needed logic in their command classes by implementing + `Predis\Command\PrefixableCommandInterface` just like before. + +- `Predis\PubSub\DispatcherLoop` now takes a `Predis\PubSub\Consumer` instance + as the sole argument of its constructor instead of `Predis\ClientInterface`. + +- All of the interfaces and classes related to translated Redis response types + have been moved in the new `Predis\Response` namespace and most of them have + been renamed to make their fully-qualified name less redundant. Now the base + response interface is `Predis\Response\ResponseInterface`. + +- Renamed interface `Predis\Command\Processor\CommandProcessorInterface` to a + shorter `Predis\Command\Processor\ProcessorInterface`. Also removed interface + for chain processors since it is basically useless. + +- Renamed `Predis\ExecutableContextInterface` to `Predis\ClientContextInterface` + and augmented it with a couple of required methods since this interface is no + more comparable to a basic client as it could be misleading. + +- The `Predis\Option` namespace is now known as `Predis\Configuration` and have + a fully-reworked `Options` class with the ability to lazily initialize values + using objects that responds to `__invoke()` (not all the kinds of callables) + even for custom options defined by the user. + +- Renamed `Predis\Connection\ConnectionInterface::writeCommand()` into + `writeRequest()` for consistency with its counterpart, `readResponse()`. + +- Renamed `Predis\Connection\SingleConnectionInterface::pushInitCommand()` into + `addConnectCommand()` which is more obvious. + +- Renamed the connection class based on both ext-phpiredis and ext-socket into + `Predis\Connection\PhpiredisSocketConnection`. The one based on PHP's streams + is still named `Predis\Connection\PhpiredisStreamConnection`. + +- Renamed the connection factory class to `Predis\Connection\Factory`. Now its + constructor does not require anymore a profile instance to create `AUTH` and + `SELECT` commands when parameters contain both `password` and `database`. Raw + commands will be used instead. + +- Renamed the connection parameters class to `Predis\Connection\Parameters`. Now + its constructor accepts only named arrays, but instances can still be created + using both URIs or arrays using the static method `Parameters::create()`. + +- The profile factory code has been extracted from the abstract Redis profile + class and now lives in `Predis\Profile\Factory`. + +- The `Predis\Connection` namespace has been completely reorganized by renaming + a few classes and interfaces and adding some sub-namespaces. + +- Most classes and interfaces in the `Predis\Protocol` namespace have been moved + or renamed while rationalizing the whole API for external protocol processors. + + +v0.8.7 (2014-08-01) +================================================================================ + +- Added `3.0` in the server profiles aliases list for Redis 3.0. `2.8` is still + the default server profile and `dev` still targets Redis 3.0. + +- Added `COMMAND` to the server profile for Redis 2.8. + +- Switched internally to the `CLUSTER SLOTS` command instead of `CLUSTER NODES` + to fetch the updated slots map from redis-cluster. This change requires users + to upgrade Redis nodes to >= 3.0.0b7. + +- The updated slots map is now fetched automatically from redis-cluster upon the + first `-MOVED` response by default. This change makes it possible to feed the + client constructor with only a few nodes of the actual cluster composition, + without needing a more complex configuration. + +- Implemented support for `PING` in PUB/SUB loop for Redis >= 3.0.0b8. + +- The default client-side sharding strategy and the one for redis-cluster now + share the same implementations as they follow the same rules. One difference, + aside from the different hashing function used to calculate distribution, is + in how empty hash tags like {} are treated by redis-cluster. + +- __FIX__: the patch applied to fix #180 introduced a regression affecting read/ + write timeouts in `Predis\Connection\PhpiredisStreamConnection`. Unfortunately + the only possible solution requires PHP 5.4+. On PHP 5.3, read/write timeouts + will be ignored from now on. + + +v0.8.6 (2014-07-15) +================================================================================ + +- Redis 2.8 is now the default server profile as there are no changes that would + break compatibility with previous releases. + +- Added `PFADD`, `PFCOUNT`, `PFMERGE` to the server profile for Redis 2.8 for + handling the HyperLogLog data structure introduced in Redis 2.8.9. + +- Added `ZLEXCOUNT`, `ZRANGEBYLEX`, `ZREMRANGEBYLEX` to the server profile for + Redis 2.8 for handling lexicographic operations on members of sorted sets. + +- Added support for key hash tags when using redis-cluster (Redis 3.0.0b1). + +- __FIX__: minor tweaks to make Predis compatible with HHVM >= 2.4.0. + +- __FIX__: responses to `INFO` are now properly parsed and will not break when + redis sentinel is being used (ISSUE #154). + +- __FIX__: added missing support for `INCRBYFLOAT` in cluster and replication + configurations (ISSUE #159). + +- __FIX__: fix parsing of the output of `CLUSTER NODES` to fetch the slots map + from a node when redis-cluster has slaves in its configuration (ISSUE #165). + +- __FIX__: prevent a stack overflow when iterating over large Redis collections + using our abstraction for cursor-based iterators (ISSUE #182). + +- __FIX__: properly discards transactions when the server immediately returns an + error response (e.g. -OOM or -ERR on invalid arguments for a command) instead + of a +QUEUED response (ISSUE #187). + +- Upgraded to PHPUnit 4.* for the test suite. + + +v0.8.5 (2014-01-16) +================================================================================ + +- Added `2.8` in the server profiles aliases list for Redis 2.8. `2.6` is still + the default server profile and `dev` now targets Redis 3.0. + +- Added `SCAN`, `SSCAN`, `ZSCAN`, `HSCAN` to the server profile for Redis 2.8. + +- Implemented PHP iterators for incremental iterations over Redis collections: + + - keyspace (cursor-based iterator using `SCAN`) + - sets (cursor-based iterator using `SSCAN`) + - sorted sets (cursor-based iterator using `ZSCAN`) + - hashes (cursor-based iterator using `HSCAN`) + - lists (plain iterator using `LRANGE`) + +- It is now possible to execute "raw commands" using `Predis\Command\RawCommand` + and a variable list of command arguments. Input arguments are not filtered and + responses are not parsed, which means arguments must follow the signature of + the command as defined by Redis and complex responses are left untouched. + +- URI parsing for connection parameters has been improved and has slightly less + overhead when the number of fields in the querystring grows. New features are: + + - Parsing does not break when value of a field contains one or more "=". + - Repeated fieldnames using [] produce an array of values. + - Empty or incomplete "key=value" pairs result in an empty string for "key". + +- Various improvements and fixes to the redis-cluster connection backend: + + - __FIX__: the `ASKING` command is sent upon -ASK redirections. + - An updated slots-map can be fetched from nodes using the `CLUSTER NODES` + command. By default this is a manual operation but can be enabled to get + automatically done upon -MOVED redirections. + - It is possible to specify a common set of connection parameters that are + applied to connections created on the fly upon redirections to nodes not + part of the initial pool. + +- List of deprecated methods: + + - `Predis\Client::multiExec()`: superseded by `Predis\Client::transaction()` + and to be removed in the next major release. + - `Predis\Client::pubSub()`: superseded by `Predis\Client::pubSubLoop()` and + to be removed in the next major release. This change was needed due to the + recently introduced `PUBSUB` command in Redis 2.8. + + +v0.8.4 (2013-07-27) +================================================================================ + +- Added `DUMP` and `RESTORE` to the server profile for Redis 2.6. + +- Connection exceptions now report basic host details in their messages. + +- Allow `Predis\Connection\PhpiredisConnection` to use a random IP when a host + actually has several IPs (ISSUE #116). + +- __FIX__: allow `HMSET` when using a cluster of Redis nodes with client-side + sharding or redis-cluster (ISSUE #106). + +- __FIX__: set `WITHSCORES` modifer for `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE` + and `ZREVRANGEBYSCORE` only when the options array passed to these commands + has `WITHSCORES` set to `true` (ISSUE #107). + +- __FIX__: scripted commands falling back from `EVALSHA` to `EVAL` resulted in + PHP errors when using a prefixed client (ISSUE #109). + +- __FIX__: `Predis\PubSub\DispatcherLoop` now works properly when using key + prefixing (ISSUE #114). + + +v0.8.3 (2013-02-18) +================================================================================ + +- Added `CLIENT SETNAME` and `CLIENT GETNAME` (ISSUE #102). + +- Implemented the `Predis\Connection\PhpiredisStreamConnection` class using the + `phpiredis` extension like `Predis\Connection\PhpiredisStreamConnection`, but + without requiring the `socket` extension since it relies on PHP's streams. + +- Added support for the TCP_NODELAY flag via the `tcp_nodelay` parameter for + stream-based connections, namely `Predis\Connection\StreamConnection` and + `Predis\Connection\PhpiredisStreamConnection` (requires PHP >= 5.4.0). + +- Updated the aggregated connection class for redis-cluster to work with 16384 + hash slots instead of 4096 to reflect the recent change from redis unstable + ([see this commit](https://github.com/antirez/redis/commit/ebd666d)). + +- The constructor of `Predis\Client` now accepts a callable as first argument + returning `Predis\Connection\ConnectionInterface`. Users can create their + own self-contained strategies to create and set up the underlying connection. + +- Users should return `0` from `Predis\Command\ScriptedCommand::getKeysCount()` + instead of `FALSE` to indicate that all of the arguments of a Lua script must + be used to populate `ARGV[]`. This does not represent a breaking change. + +- The `Predis\Helpers` class has been deprecated and it will be removed in + future releases. + + +v0.8.2 (2013-02-03) +================================================================================ + +- Added `Predis\Session\SessionHandler` to make it easy to store PHP sessions + on Redis using Predis. Please note that this class needs either PHP >= 5.4.0 + or a polyfill for PHP's `SessionHandlerInterface`. + +- Added the ability to get the default value of a client option directly from + `Predis\Option\ClientOption` using the `getDefault()` method by passing the + option name or its instance. + +- __FIX__: the standard pipeline executor was not using the response parser + methods associated to commands to process raw responses (ISSUE #101). + + +v0.8.1 (2013-01-19) +================================================================================ + +- The `connections` client option can now accept a callable object returning + an instance of `Predis\Connection\ConnectionFactoryInterface`. + +- Client options accepting callable objects as factories now pass their actual + instance to the callable as the second argument. + +- `Predis\Command\Processor\KeyPrefixProcessor` can now be directly casted to + string to obtain the current prefix, useful with string interpolation. + +- Added an optional callable argument to `Predis\Cluster\Distribution\HashRing` + and `Predis\Cluster\Distribution\KetamaPureRing` constructor that can be used + to customize how the distributor should extract the connection hash when + initializing the nodes distribution (ISSUE #36). + +- Correctly handle `TTL` and `PTTL` returning -2 on non existing keys starting + with Redis 2.8. + +- __FIX__: a missing use directive in `Predis\Transaction\MultiExecContext` + caused PHP errors when Redis did not return `+QUEUED` replies to commands + when inside a MULTI / EXEC context. + +- __FIX__: the `parseResponse()` method implemented for a scripted command was + ignored when retrying to execute a Lua script by falling back to `EVAL` after + a `-NOSCRIPT` error (ISSUE #94). + +- __FIX__: when subclassing `Predis\Client` the `getClientFor()` method returns + a new instance of the subclass instead of a new instance of `Predis\Client`. + + +v0.8.0 (2012-10-23) +================================================================================ + +- The default server profile for Redis is now `2.6`. + +- Certain connection parameters have been renamed: + + - `connection_async` is now `async_connect` + - `connection_timeout` is now `timeout` + - `connection_persistent` is now `persistent` + +- The `throw_errors` connection parameter has been removed and replaced by the + new `exceptions` client option since exceptions on `-ERR` replies returned by + Redis are not generated by connection classes anymore but instead are thrown + by the client class and other abstractions such as pipeline contexts. + +- Added smart support for redis-cluster (Redis v3.0) in addition to the usual + cluster implementation that uses client-side sharding. + +- Various namespaces and classes have been renamed to follow rules inspired by + the Symfony2 naming conventions. + +- The second argument of the constructor of `Predis\Client` does not accept + strings or instances of `Predis\Profile\ServerProfileInterface` anymore. + To specify a server profile you must explicitly set `profile` in the array + of client options. + +- `Predis\Command\ScriptedCommand` internally relies on `EVALSHA` instead of + `EVAL` thus avoiding to send Lua scripts bodies on each request. The client + automatically resends the command falling back to `EVAL` when Redis returns a + `-NOSCRIPT` error. Automatic fallback to `EVAL` does not work with pipelines, + inside a `MULTI / EXEC` context or with plain `EVALSHA` commands. + +- Complex responses are no more parsed by connection classes as they must be + processed by consumer classes using the handler associated to the issued + command. This means that executing commands directly on connections only + returns simple Redis types, but nothing changes when using `Predis\Client` + or the provided abstractions for pipelines and transactions. + +- Iterators for multi-bulk replies now skip the response parsing method of the + command that generated the response and are passed directly to user code. + Pipeline and transaction objects still consume automatically iterators. + +- Cluster and replication connections now extend a new common interface, + `Predis\Connection\AggregatedConnectionInterface`. + +- `Predis\Connection\MasterSlaveReplication` now uses an external strategy + class to handle the logic for checking readable / writable commands and Lua + scripts. + +- Command pipelines have been optimized for both speed and code cleanness, but + at the cost of bringing a breaking change in the signature of the interface + for pipeline executors. + +- Added a new pipeline executor that sends commands wrapped in a MULTI / EXEC + context to make the execution atomic: if a pipeline fails at a certain point + then the whole pipeline is discarded. + +- The key-hashing mechanism for commands is now handled externally and is no + more a competence of each command class. This change is neeeded to support + both client-side sharding and Redis cluster. + +- `Predis\Options\Option` is now abstract, see `Predis\Option\AbstractOption`. + + +v0.7.3 (2012-06-01) +================================================================================ + +- New commands available in the Redis v2.6 profile (dev): `BITOP`, `BITCOUNT`. + +- When the number of keys `Predis\Commands\ScriptedCommand` is negative, Predis + will count from the end of the arguments list to calculate the actual number + of keys that will be interpreted as elements for `KEYS` by the underlying + `EVAL` command. + +- __FIX__: `examples\CustomDistributionStrategy.php` had a mistyped constructor + call and produced a bad distribution due to an error as pointed in ISSUE #63. + This bug is limited to the above mentioned example and does not affect the + classes implemented in the `Predis\Distribution` namespace. + +- __FIX__: `Predis\Commands\ServerEvalSHA::getScriptHash()` was calculating the + hash while it just needs to return the first argument of the command. + +- __FIX__: `Predis\Autoloader` has been modified to allow cascading autoloaders + for the `Predis` namespace. + + +v0.7.2 (2012-04-01) +================================================================================ + +- Added `2.6` in the server profiles aliases list for the upcoming Redis 2.6. + `2.4` is still the default server profile. `dev` now targets Redis 2.8. + +- Connection instances can be serialized and unserialized using `serialize()` + and `unserialize()`. This is handy in certain scenarios such as client-side + clustering or replication to lower the overhead of initializing a connection + object with many sub-connections since unserializing them can be up to 5x + times faster. + +- Reworked the default autoloader to make it faster. It is also possible to + prepend it in PHP's autoload stack. + +- __FIX__: fixed parsing of the payload returned by `MONITOR` with Redis 2.6. + + +v0.7.1 (2011-12-27) +================================================================================ + +- The PEAR channel on PearHub has been deprecated in favour of `pear.nrk.io`. + +- Miscellaneous minor fixes. + +- Added transparent support for master / slave replication configurations where + write operations are performed on the master server and read operations are + routed to one of the slaves. Please refer to ISSUE #21 for a bit of history + and more details about replication support in Predis. + +- The `profile` client option now accepts a callable object used to initialize + a new instance of `Predis\Profiles\IServerProfile`. + +- Exposed a method for MULTI / EXEC contexts that adds the ability to execute + instances of Redis commands against transaction objects. + + +v0.7.0 (2011-12-11) +================================================================================ + +- Predis now adheres to the PSR-0 standard which means that there is no more a + single file holding all the classes of the library, but multiple files (one + for each class). You can use any PSR-0 compatible autoloader to load Predis + or just leverage the default one shipped with the library by requiring the + `Predis/Autoloader.php` and call `Predis\Autoloader::register()`. + +- The default server profile for Redis is now 2.4. The `dev` profile supports + all the features of Redis 2.6 (currently unstable) such as Lua scripting. + +- Support for long aliases (method names) for Redis commands has been dropped. + +- Redis 1.0 is no more supported. From now on Predis will use only the unified + protocol to serialize commands. + +- It is possible to prefix keys transparently on a client-level basis with the + new `prefix` client option. + +- An external connection factory is used to initialize new connection instances + and developers can now register their own connection classes using the new + `connections` client option. + +- It is possible to connect locally to Redis using UNIX domain sockets. Just + use `unix:///path/to/redis.sock` or a named array just like in the following + example: `array('scheme' => 'unix', 'path' => '/path/to/redis.sock');`. + +- If the `phpiredis` extension is loaded by PHP, it is now possible to use an + alternative connection class that leverages it to make Predis faster on many + cases, especially when dealing with big multibulk replies, with the the only + downside that persistent connections are not supported. Please refer to the + documentation to see how to activate this class using the new `connections` + client option. + +- Predis is capable to talk with Webdis, albeit with some limitations such as + the lack of pipelining and transactions, just by using the `http` scheme in + in the connection parameters. All is needed is PHP with the `curl` and the + `phpiredis` extensions loaded. + +- Way too many changes in the public API to make a list here, we just tried to + make all the Redis commands compatible with previous releases of v0.6 so that + you do not have to worry if you are simply using Predis as a client. Probably + the only breaking changes that should be mentioned here are: + + - `throw_on_error` has been renamed to `throw_errors` and it is a connection + parameter instead of a client option, along with `iterable_multibulk`. + + - `key_distribution` has been removed from the client options. To customize + the distribution strategy you must provide a callable object to the new + `cluster` client option to configure and then return a new instance of + `Predis\Network\IConnectionCluster`. + + - `Predis\Client::create()` has been removed. Just use the constructor to set + up a new instance of `Predis\Client`. + + - `Predis\Client::pipelineSafe()` was deprecated in Predis v0.6.1 and now has + finally removed. Use `Predis\Client::pipeline(array('safe' => true))`. + + - `Predis\Client::rawCommand()` has been removed due to inconsistencies with + the underlying connection abstractions. You can still get the raw resource + out of a connection with `Predis\Network\IConnectionSingle::getResource()` + so that you can talk directly with Redis. + +- The `Predis\MultiBulkCommand` class has been merged into `Predis\Command` and + thus removed. Serialization of commands is now a competence of connections. + +- The `Predis\IConnection` interface has been splitted into two new interfaces: + `Predis\Network\IConnectionSingle` and `Predis\Network\IConnectionCluster`. + +- The constructor of `Predis\Client` now accepts more type of arguments such as + instances of `Predis\IConnectionParameters` and `Predis\Network\IConnection`. + + +v0.6.6 (2011-04-01) +================================================================================ + +- Switched to Redis 2.2 as the default server profile (there are no changes + that would break compatibility with previous releases). Long command names + are no more supported by default but if you need them you can still require + `Predis_Compatibility.php` to avoid breaking compatibility. + +- Added a `VERSION` constant to `Predis\Client`. + +- Some performance improvements for multibulk replies (parsing them is about + 16% faster than the previous version). A few core classes have been heavily + optimized to reduce overhead when creating new instances. + +- Predis now uses by default a new protocol reader, more lightweight and + faster than the default handler-based one. Users can revert to the old + protocol reader with the `reader` client option set to `composable`. + This client option can also accept custom reader classes implementing the + new `Predis\IResponseReader` interface. + +- Added support for connecting to Redis using UNIX domain sockets (ISSUE #25). + +- The `read_write_timeout` connection parameter can now be set to 0 or false + to disable read and write timeouts on connections. The old behaviour of -1 + is still intact. + +- `ZUNIONSTORE` and `ZINTERSTORE` can accept an array to specify a list of the + source keys to be used to populate the destination key. + +- `MGET`, `SINTER`, `SUNION` and `SDIFF` can accept an array to specify a list + of keys. `SINTERSTORE`, `SUNIONSTORE` and `SDIFFSTORE` can also accept an + array to specify the list of source keys. + +- `SUBSCRIBE` and `PSUBSCRIBE` can accept a list of channels for subscription. + +- __FIX__: some client-side clean-ups for `MULTI/EXEC` were handled incorrectly + in a couple of corner cases (ISSUE #27). + + +v0.6.5 (2011-02-12) +================================================================================ + +- __FIX__: due to an untested internal change introduced in v0.6.4, a wrong + handling of bulk reads of zero-length values was producing protocol + desynchronization errors (ISSUE #20). + + +v0.6.4 (2011-02-12) +================================================================================ + +- Various performance improvements (15% ~ 25%) especially when dealing with + long multibulk replies or when using clustered connections. + +- Added the `on_retry` option to `Predis\MultiExecBlock` that can be used to + specify an external callback (or any callable object) that gets invoked + whenever a transaction is aborted by the server. + +- Added inline (p)subscribtion via options when initializing an instance of + `Predis\PubSubContext`. + + +v0.6.3 (2011-01-01) +================================================================================ + +- New commands available in the Redis v2.2 profile (dev): + - Strings: `SETRANGE`, `GETRANGE`, `SETBIT`, `GETBIT` + - Lists : `BRPOPLPUSH` + +- The abstraction for `MULTI/EXEC` transactions has been dramatically improved + by providing support for check-and-set (CAS) operations when using Redis >= + 2.2. Aborted transactions can also be optionally replayed in automatic up + to a user-defined number of times, after which a `Predis\AbortedMultiExec` + exception is thrown. + + +v0.6.2 (2010-11-28) +================================================================================ + +- Minor internal improvements and clean ups. + +- New commands available in the Redis v2.2 profile (dev): + - Strings: `STRLEN` + - Lists : `LINSERT`, `RPUSHX`, `LPUSHX` + - ZSets : `ZREVRANGEBYSCORE` + - Misc. : `PERSIST` + +- WATCH also accepts a single array parameter with the keys that should be + monitored during a transaction. + +- Improved the behaviour of `Predis\MultiExecBlock` in certain corner cases. + +- Improved parameters checking for the SORT command. + +- __FIX__: the `STORE` parameter for the `SORT` command didn't work correctly + when using `0` as the target key (ISSUE #13). + +- __FIX__: the methods for `UNWATCH` and `DISCARD` do not break anymore method + chaining with `Predis\MultiExecBlock`. + + +v0.6.1 (2010-07-11) +================================================================================ + +- Minor internal improvements and clean ups. + +- New commands available in the Redis v2.2 profile (dev): + - Misc. : `WATCH`, `UNWATCH` + +- Optional modifiers for `ZRANGE`, `ZREVRANGE` and `ZRANGEBYSCORE` queries are + supported using an associative array passed as the last argument of their + respective methods. + +- The `LIMIT` modifier for `ZRANGEBYSCORE` can be specified using either: + - an indexed array: `array($offset, $count)` + - an associative array: `array('offset' => $offset, 'count' => $count)` + +- The method `Predis\Client::__construct()` now accepts also instances of + `Predis\ConnectionParameters`. + +- `Predis\MultiExecBlock` and `Predis\PubSubContext` now throw an exception + when trying to create their instances using a profile that does not + support the required Redis commands or when the client is connected to + a cluster of connections. + +- Various improvements to `Predis\MultiExecBlock`: + - fixes and more consistent behaviour across various usage cases. + - support for `WATCH` and `UNWATCH` when using the current development + profile (Redis v2.2) and aborted transactions. + +- New signature for `Predis\Client::multiExec()` which is now able to accept + an array of options for the underlying instance of `Predis\MultiExecBlock`. + Backwards compatibility with previous releases of Predis is ensured. + +- New signature for `Predis\Client::pipeline()` which is now able to accept + an array of options for the underlying instance of Predis\CommandPipeline. + Backwards compatibility with previous releases of Predis is ensured. + The method `Predis\Client::pipelineSafe()` is to be considered deprecated. + +- __FIX__: The `WEIGHT` modifier for `ZUNIONSTORE` and `ZINTERSTORE` was + handled incorrectly with more than two weights specified. + + +v0.6.0 (2010-05-24) +================================================================================ + +- Switched to the new multi-bulk request protocol for all of the commands + in the Redis 1.2 and Redis 2.0 profiles. Inline and bulk requests are now + deprecated as they will be removed in future releases of Redis. + +- The default server profile is `2.0` (targeting Redis 2.0.x). If you are + using older versions of Redis, it is highly recommended that you specify + which server profile the client should use (e.g. `1.2` when connecting + to instances of Redis 1.2.x). + +- Support for Redis 1.0 is now optional and it is provided by requiring + 'Predis_Compatibility.php' before creating an instance of `Predis\Client`. + +- New commands added to the Redis 2.0 profile since Predis 0.5.1: + - Strings: `SETEX`, `APPEND`, `SUBSTR` + - ZSets : `ZCOUNT`, `ZRANK`, `ZUNIONSTORE`, `ZINTERSTORE`, `ZREMBYRANK`, + `ZREVRANK` + - Hashes : `HSET`, `HSETNX`, `HMSET`, `HINCRBY`, `HGET`, `HMGET`, `HDEL`, + `HEXISTS`, `HLEN`, `HKEYS`, `HVALS`, `HGETALL` + - PubSub : `PUBLISH`, `SUBSCRIBE`, `UNSUBSCRIBE` + - Misc. : `DISCARD`, `CONFIG` + +- Introduced client-level options with the new `Predis\ClientOptions` class. + Options can be passed to the constructor of `Predis\Client` in its second + argument as an array or an instance of `Predis\ClientOptions`. For brevity's + sake and compatibility with older versions, the constructor still accepts + an instance of `Predis\RedisServerProfile` in its second argument. The + currently supported client options are: + + - `profile` [default: `2.0` as of Predis 0.6.0]: specifies which server + profile to use when connecting to Redis. This option accepts an instance + of `Predis\RedisServerProfile` or a string that indicates the version. + + - `key_distribution` [default: `Predis\Distribution\HashRing`]: specifies + which key distribution strategy to use to distribute keys among the + servers that compose a cluster. This option accepts an instance of + `Predis\Distribution\IDistributionStrategy` so that users can implement + their own key distribution strategy. `Predis\Distribution\KetamaPureRing` + is an alternative distribution strategy providing a pure-PHP implementation + of the same algorithm used by libketama. + + - `throw_on_error` [default: `TRUE`]: server errors can optionally be handled + "silently": instead of throwing an exception, the client returns an error + response type. + + - `iterable_multibulk` [EXPERIMENTAL - default: `FALSE`]: in addition to the + classic way of fetching a whole multibulk reply into an array, the client + can now optionally stream a multibulk reply down to the user code by using + PHP iterators. It is just a little bit slower, but it can save a lot of + memory in certain scenarios. + +- New parameters for connections: + + - `alias` [default: not set]: every connection can now be identified by an + alias that is useful to get a specific connections when connected to a + cluster of Redis servers. + - `weight` [default: not set]: allows to balance keys asymmetrically across + multiple servers. This is useful when you have servers with different + amounts of memory to distribute the load of your keys accordingly. + - `connection_async` [default: `FALSE`]: estabilish connections to servers + in a non-blocking way, so that the client is not blocked while the socket + resource performs the actual connection. + - `connection_persistent` [default: `FALSE`]: the underlying socket resource + is left open when a script ends its lifecycle. Persistent connections can + lead to unpredictable or strange behaviours, so they should be used with + extreme care. + +- Introduced the `Predis\Pipeline\IPipelineExecutor` interface. Classes that + implements this interface are used internally by the `Predis\CommandPipeline` + class to change the behaviour of the pipeline when writing/reading commands + from one or multiple servers. Here is the list of the default executors: + + - `Predis\Pipeline\StandardExecutor`: exceptions generated by server errors + might be thrown depending on the options passed to the client (see the + `throw_on_error` client option). Instead, protocol or network errors always + throw exceptions. This is the default executor for single and clustered + connections and shares the same behaviour of Predis 0.5.x. + - `Predis\Pipeline\SafeExecutor`: exceptions generated by server, protocol + or network errors are not thrown but returned in the response array as + instances of `Predis\ResponseError` or `Predis\CommunicationException`. + - `Predis\Pipeline\SafeClusterExecutor`: this executor shares the same + behaviour of `Predis\Pipeline\SafeExecutor` but it is geared towards + clustered connections. + +- Support for PUB/SUB is handled by the new `Predis\PubSubContext` class, which + could also be used to build a callback dispatcher for PUB/SUB scenarios. + +- When connected to a cluster of connections, it is now possible to get a + new `Predis\Client` instance for a single connection of the cluster by + passing its alias/index to the new `Predis\Client::getClientFor()` method. + +- `Predis\CommandPipeline` and `Predis\MultiExecBlock` return their instances + when invokink commands, thus allowing method chaining in pipelines and + multi-exec blocks. + +- `Predis\MultiExecBlock` can handle the new `DISCARD` command. + +- Connections now support float values for the `connection_timeout` parameter + to express timeouts with a microsecond resolution. + +- __FIX__: TCP connections now respect the read/write timeout parameter when + reading the payload of server responses. Previously, `stream_get_contents()` + was being used internally to read data from a connection but it looks like + PHP does not honour the specified timeout for socket streams when inside + this function. + +- __FIX__: The `GET` parameter for the `SORT` command now accepts also multiple + key patterns by passing an array of strings. (ISSUE #1). + +* __FIX__: Replies to the `DEL` command return the number of elements deleted + by the server and not 0 or 1 interpreted as a boolean response. (ISSUE #4). + + +v0.5.1 (2010-01-23) +================================================================================ + +* `RPOPLPUSH` has been changed from bulk command to inline command in Redis + 1.2.1, so `ListPopLastPushHead` now extends `InlineCommand`. The old behavior + is still available via the `ListPopLastPushHeadBulk` class so that you can + override the server profile if you need the old (and uncorrect) behaviour + when connecting to a Redis 1.2.0 instance. + +* Added missing support for `BGREWRITEAOF` for Redis >= 1.2.0. + +* Implemented a factory method for the `RedisServerProfile` class to ease the + creation of new server profile instances based on a version string. + + +v0.5.0 (2010-01-09) +================================================================================ +* First versioned release of Predis diff --git a/vendor/predis/predis/CONTRIBUTING.md b/vendor/predis/predis/CONTRIBUTING.md new file mode 100644 index 0000000..8da3339 --- /dev/null +++ b/vendor/predis/predis/CONTRIBUTING.md @@ -0,0 +1,44 @@ +## Filing bug reports ## + +Bugs or feature requests can be posted on the [GitHub issues](http://github.com/nrk/predis/issues) +section of the project. + +When reporting bugs, in addition to the obvious description of your issue you __must__ always provide +some essential information about your environment such as: + + 1. version of Predis (check the `VERSION` file or the `Predis\Client::VERSION` constant). + 2. version of Redis (check `redis_version` returned by [`INFO`](http://redis.io/commands/info)). + 3. version of PHP. + 4. name and version of the operating system. + 5. when possible, a small snippet of code that reproduces the issue. + +__Think about it__: we do not have a crystal ball and cannot predict things or peer into the unknown +so please provide as much details as possible to help us isolating issues and fix them. + +__Never__ use GitHub issues to post generic questions about Predis! When you have questions about +how Predis works or how it can be used, please just hop me an email and I will get back to you as +soon as possible. + + +## Contributing code ## + +If you want to work on Predis, it is highly recommended that you first run the test suite in order +to check that everything is OK and report strange behaviours or bugs. When modifying Predis please +make sure that no warnings or notices are emitted by PHP running the interpreter in your development +environment with the `error_reporting` variable set to `E_ALL | E_STRICT`. + +The recommended way to contribute to Predis is to fork the project on GitHub, create topic branches +on your newly created repository to fix bugs or add new features (possibly with tests covering your +modifications) and then open a pull request with a description of the applied changes. Obviously you +can use any other Git hosting provider of your preference. + +We always aim for consistency in our code base so you should follow basic coding rules as defined by +[PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) +and [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) +and stick with the conventions used in Predis to name classes and interfaces. Indentation should be +done with 4 spaces and code should be wrapped at 100 columns (please try to stay within this limit +even if the above mentioned official coding guidelines set the soft limit to 120 columns). + +Please follow these [commit guidelines](http://git-scm.com/book/ch5-2.html#Commit-Guidelines) when +committing your code to Git and always write a meaningful (not necessarily extended) description of +your changes before opening pull requests. diff --git a/vendor/predis/predis/FAQ.md b/vendor/predis/predis/FAQ.md new file mode 100644 index 0000000..5c560b4 --- /dev/null +++ b/vendor/predis/predis/FAQ.md @@ -0,0 +1,177 @@ +# Some frequently asked questions about Predis # +________________________________________________ + +### What is the point of Predis? ### + +The main point of Predis is about offering a highly customizable and extensible client for Redis, +that can be easily extended by developers while still being reasonabily fast. With Predis you can +swap almost any class with your own custom implementation: you can have custom connection classes, +new distribution strategies for client-side sharding, or handlers to replace or add Redis commands. +All of this can be achieved without messing with the source code of the library and directly in your +own application. Given the fast pace at which Redis is developed and adds new features, this can be +a great asset since it allows developers to add new and still missing features or commands or change +the standard behaviour of the library without the need to break dependencies in production code (at +least to some degree). + +### Does Predis support UNIX domain sockets and persistent connections? ### + +Yes. Obviously persistent connections actually work only when using PHP configured as a persistent +process reused by the web server (see [PHP-FPM](http://php-fpm.org)). + +### Does Predis support SSL-encrypted connections? ### + +Yes. Encrypted connections are mostly useful when connecting to Redis instances exposed by various +cloud hosting providers without the need to configure an SSL proxy, but you should also take into +account the general performances degradation especially during the connect() operation when the TLS +handshake must be performed to secure the connection. Persistent SSL-encrypted connections may help +in that respect, but they are supported only when running on PHP >= 7.0.0. + +### Does Predis support transparent (de)serialization of values? ### + +No and it will not ever do that by default. The reason behind this decision is that serialization is +usually something that developers prefer to customize depending on their needs and can not be easily +generalized when using Redis because of the many possible access patterns for your data. This does +not mean that it is impossible to have such a feature since you can leverage the extensibility of +this library to define your own serialization-aware commands. You can find more details about how to +do that [on this issue](http://github.com/nrk/predis/issues/29#issuecomment-1202624). + +### How can I force Predis to connect to Redis before sending any command? ### + +Explicitly connecting to Redis is usually not needed since the client initializes connections lazily +only when they are needed. Admittedly, this behavior can be inconvenient in certain scenarios when +you absolutely need to perform an upfront check to determine if the server is up and running and +eventually catch exceptions on failures. Forcing the client to open the underlying connection can be +done by invoking `Predis\Client::connect()`: + +```php +$client = new Predis\Client(); + +try { + $client->connect(); +} catch (Predis\Connection\ConnectionException $exception) { + // We could not connect to Redis! Your handling code goes here. +} + +$client->info(); +``` + +### How Predis abstracts Redis commands? ### + +The approach used to implement Redis commands is quite simple: by default each command follows the +same signature as defined on the [Redis documentation](http://redis.io/commands) which makes things +pretty easy if you already know how Redis works or you need to look up how to use certain commands. +Alternatively, variadic commands can accept an array for keys or values (depending on the command) +instead of a list of arguments. Commands such as [`RPUSH`](http://redis.io/commands/rpush) and +[`HMSET`](http://redis.io/commands/hmset) are great examples: + +```php +$client->rpush('my:list', 'value1', 'value2', 'value3'); // plain method arguments +$client->rpush('my:list', ['value1', 'value2', 'value3']); // single argument array + +$client->hmset('my:hash', 'field1', 'value1', 'field2', 'value2'); // plain method arguments +$client->hmset('my:hash', ['field1'=>'value1', 'field2'=>'value2']); // single named array +``` + +An exception to this rule is [`SORT`](http://redis.io/commands/sort) for which modifiers are passed +[using a named array](tests/Predis/Command/KeySortTest.php#L54-L75). + + +# Speaking about performances... # +_________________________________________________ + + +### Predis is a pure-PHP implementation: it can not be fast enough! ### + +It really depends, but most of the times the answer is: _yes, it is fast enough_. I will give you a +couple of easy numbers with a simple test that uses a single client and is executed by PHP 5.5.6 +against a local instance of Redis 2.8 that runs under Ubuntu 13.10 on a Intel Q6600: + +``` +21000 SET/sec using 12 bytes for both key and value. +21000 GET/sec while retrieving the very same values. +0.130 seconds to fetch 30000 keys using _KEYS *_. +``` + +How does it compare with [__phpredis__](http://github.com/nicolasff/phpredis), a nice C extension +providing an efficient client for Redis? + +``` +30100 SET/sec using 12 bytes for both key and value +29400 GET/sec while retrieving the very same values +0.035 seconds to fetch 30000 keys using "KEYS *"". +``` + +Wow __phpredis__ seems much faster! Well, we are comparing a C extension with a pure-PHP library so +lower numbers are quite expected but there is a fundamental flaw in them: is this really how you are +going to use Redis in your application? Are you really going to send thousands of commands using a +for-loop on each page request using a single client instance? If so... well I guess you are probably +doing something wrong. Also, if you need to `SET` or `GET` multiple keys you should definitely use +commands such as `MSET` and `MGET`. You can also use pipelining to get more performances when this +technique can be used. + +There is one more thing: we have tested the overhead of Predis by connecting on a localhost instance +of Redis but how these numbers change when we hit the physical network by connecting to remote Redis +instances? + +``` +Using Predis: +3200 SET/sec using 12 bytes for both key and value +3200 GET/sec while retrieving the very same values +0.132 seconds to fetch 30000 keys using "KEYS *". + +Using phpredis: +3500 SET/sec using 12 bytes for both key and value +3500 GET/sec while retrieving the very same values +0.045 seconds to fetch 30000 keys using "KEYS *". +``` + +There you go, you get almost the same average numbers and the reason is simple: network latency is a +real performance killer and you cannot do (almost) anything about that. As a disclaimer, remember +that we are measuring the overhead of client libraries implementations and the effects of network +round-trip times, so we are not really measuring how fast Redis is. Redis shines best with thousands +of concurrent clients doing requests! Also, actual performances should be measured according to how +your application will use Redis. + +### I am convinced, but performances for multi-bulk responses are still worse ### + +Fair enough, but there is an option available if you need even more speed and consists on installing +__[phpiredis](http://github.com/nrk/phpiredis)__ (note the additional _i_ in the name) and let the +client use it. __phpiredis__ is another C extension that wraps __hiredis__ (the official C client +library for Redis) with a thin layer exposing its features to PHP. You can then choose between two +different connection classes: + + - `Predis\Connection\PhpiredisStreamConnection` (using native PHP streams). + - `Predis\Connection\PhpiredisSocketConnection` (requires `ext-socket`). + +You will now get the benefits of a faster protocol serializer and parser just by adding a couple of +lines of code: + +```php +$client = new Predis\Client('tcp://127.0.0.1', array( + 'connections' => array( + 'tcp' => 'Predis\Connection\PhpiredisStreamConnection', + 'unix' => 'Predis\Connection\PhpiredisSocketConnection', + ), +)); +``` + +Dead simple. Nothing changes in the way you use the library in your application. So how fast is it +our basic benchmark script now? There are not much improvements for inline or short bulk responses +like the ones returned by `SET` and `GET`, but the speed for parsing multi-bulk responses is now on +par with phpredis: + +``` +Fatching 30000 keys with _KEYS *_ using Predis paired with phpiredis:: + +0.035 seconds from a local Redis instance +0.047 seconds from a remote Redis instance +``` + +### If I need an extension to get better performances, why not using phpredis? ### + +Good question. Generically speaking if you need absolute uber-speed using Redis on the localhost and +you do not care about abstractions built around some Redis features such as MULTI / EXEC, or if you +do not need any kind of extensibility or guaranteed backwards compatibility with different versions +of Redis (Predis currently supports from 1.2 up to 2.8 and the current development version), then +using __phpredis__ makes absolutely sense. Otherwise, Predis is perfect for the job and by adding +__phpiredis__ you can get a nice speed bump almost for free. diff --git a/vendor/predis/predis/LICENSE b/vendor/predis/predis/LICENSE new file mode 100644 index 0000000..c35b657 --- /dev/null +++ b/vendor/predis/predis/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2009-2016 Daniele Alessandri + +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/predis/predis/README.md b/vendor/predis/predis/README.md new file mode 100644 index 0000000..fa9740f --- /dev/null +++ b/vendor/predis/predis/README.md @@ -0,0 +1,492 @@ +# Predis # + +[![Software license][ico-license]](LICENSE) +[![Latest stable][ico-version-stable]][link-packagist] +[![Latest development][ico-version-dev]][link-packagist] +[![Monthly installs][ico-downloads-monthly]][link-downloads] +[![Build status][ico-travis]][link-travis] +[![HHVM support][ico-hhvm]][link-hhvm] +[![Gitter room][ico-gitter]][link-gitter] + +Flexible and feature-complete [Redis](http://redis.io) client for PHP >= 5.3 and HHVM >= 2.3.0. + +Predis does not require any additional C extension by default, but it can be optionally paired with +[phpiredis](https://github.com/nrk/phpiredis) to lower the overhead of the serialization and parsing +of the [Redis RESP Protocol](http://redis.io/topics/protocol). For an __experimental__ asynchronous +implementation of the client you can refer to [Predis\Async](https://github.com/nrk/predis-async). + +More details about this project can be found on the [frequently asked questions](FAQ.md). + + +## Main features ## + +- Support for different versions of Redis (from __2.0__ to __3.2__) using profiles. +- Support for clustering using client-side sharding and pluggable keyspace distributors. +- Support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0). +- Support for master-slave replication setups and [redis-sentinel](http://redis.io/topics/sentinel). +- Transparent key prefixing of keys using a customizable prefix strategy. +- Command pipelining on both single nodes and clusters (client-side sharding only). +- Abstraction for Redis transactions (Redis >= 2.0) and CAS operations (Redis >= 2.2). +- Abstraction for Lua scripting (Redis >= 2.6) and automatic switching between `EVALSHA` or `EVAL`. +- Abstraction for `SCAN`, `SSCAN`, `ZSCAN` and `HSCAN` (Redis >= 2.8) based on PHP iterators. +- Connections are established lazily by the client upon the first command and can be persisted. +- Connections can be established via TCP/IP (also TLS/SSL-encrypted) or UNIX domain sockets. +- Support for [Webdis](http://webd.is) (requires both `ext-curl` and `ext-phpiredis`). +- Support for custom connection classes for providing different network or protocol backends. +- Flexible system for defining custom commands and profiles and override the default ones. + + +## How to _install_ and use Predis ## + +This library can be found on [Packagist](http://packagist.org/packages/predis/predis) for an easier +management of projects dependencies using [Composer](http://packagist.org/about-composer) or on our +[own PEAR channel](http://pear.nrk.io) for a more traditional installation using PEAR. Ultimately, +compressed archives of each release are [available on GitHub](https://github.com/nrk/predis/tags). + + +### Loading the library ### + +Predis relies on the autoloading features of PHP to load its files when needed and complies with the +[PSR-4 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md). +Autoloading is handled automatically when dependencies are managed through Composer, but it is also +possible to leverage its own autoloader in projects or scripts lacking any autoload facility: + +```php +// Prepend a base path if Predis is not available in your "include_path". +require 'Predis/Autoloader.php'; + +Predis\Autoloader::register(); +``` + +It is also possible to create a [phar](http://www.php.net/manual/en/intro.phar.php) archive directly +from the repository by launching the `bin/create-phar` script. The generated phar already contains a +stub defining its own autoloader, so you just need to `require()` it to start using the library. + + +### Connecting to Redis ### + +When creating a client instance without passing any connection parameter, Predis assumes `127.0.0.1` +and `6379` as default host and port. The default timeout for the `connect()` operation is 5 seconds: + +```php +$client = new Predis\Client(); +$client->set('foo', 'bar'); +$value = $client->get('foo'); +``` + +Connection parameters can be supplied either in the form of URI strings or named arrays. The latter +is the preferred way to supply parameters, but URI strings can be useful when parameters are read +from non-structured or partially-structured sources: + +```php +// Parameters passed using a named array: +$client = new Predis\Client([ + 'scheme' => 'tcp', + 'host' => '10.0.0.1', + 'port' => 6379, +]); + +// Same set of parameters, passed using an URI string: +$client = new Predis\Client('tcp://10.0.0.1:6379'); +``` + +It is also possible to connect to local instances of Redis using UNIX domain sockets, in this case +the parameters must use the `unix` scheme and specify a path for the socket file: + +```php +$client = new Predis\Client(['scheme' => 'unix', 'path' => '/path/to/redis.sock']); +$client = new Predis\Client('unix:/path/to/redis.sock'); +``` + +The client can leverage TLS/SSL encryption to connect to secured remote Redis instances without the +need to configure an SSL proxy like stunnel. This can be useful when connecting to nodes running on +various cloud hosting providers. Encryption can be enabled with using the `tls` scheme and an array +of suitable [options](http://php.net/manual/context.ssl.php) passed via the `ssl` parameter: + +```php +// Named array of connection parameters: +$client = new Predis\Client([ + 'scheme' => 'tls', + 'ssl' => ['cafile' => 'private.pem', 'verify_peer' => true], +] + +// Same set of parameters, but using an URI string: +$client = new Predis\Client('tls://127.0.0.1?ssl[cafile]=private.pem&ssl[verify_peer]=1'); +``` + +The connection schemes [`redis`](http://www.iana.org/assignments/uri-schemes/prov/redis) (alias of +`tcp`) and [`rediss`](http://www.iana.org/assignments/uri-schemes/prov/rediss) (alias of `tls`) are +also supported, with the difference that URI strings containing these schemes are parsed following +the rules described on their respective IANA provisional registration documents. + +The actual list of supported connection parameters can vary depending on each connection backend so +it is recommended to refer to their specific documentation or implementation for details. + +When an array of connection parameters is provided, Predis automatically works in cluster mode using +client-side sharding. Both named arrays and URI strings can be mixed when providing configurations +for each node: + +```php +$client = new Predis\Client([ + 'tcp://10.0.0.1?alias=first-node', + ['host' => '10.0.0.2', 'alias' => 'second-node'], +]); +``` + +See the [aggregate connections](#aggregate-connections) section of this document for more details. + +Connections to Redis are lazy meaning that the client connects to a server only if and when needed. +While it is recommended to let the client do its own stuff under the hood, there may be times when +it is still desired to have control of when the connection is opened or closed: this can easily be +achieved by invoking `$client->connect()` and `$client->disconnect()`. Please note that the effect +of these methods on aggregate connections may differ depending on each specific implementation. + + +### Client configuration ### + +Many aspects and behaviors of the client can be configured by passing specific client options to the +second argument of `Predis\Client::__construct()`: + +```php +$client = new Predis\Client($parameters, ['profile' => '2.8', 'prefix' => 'sample:']); +``` + +Options are managed using a mini DI-alike container and their values can be lazily initialized only +when needed. The client options supported by default in Predis are: + + - `profile`: specifies the profile to use to match a specific version of Redis. + - `prefix`: prefix string automatically applied to keys found in commands. + - `exceptions`: whether the client should throw or return responses upon Redis errors. + - `connections`: list of connection backends or a connection factory instance. + - `cluster`: specifies a cluster backend (`predis`, `redis` or callable object). + - `replication`: specifies a replication backend (`TRUE`, `sentinel` or callable object). + - `aggregate`: overrides `cluster` and `replication` to provide a custom connections aggregator. + - `parameters`: list of default connection parameters for aggregate connections. + +Users can also provide custom options with values or callable objects (for lazy initialization) that +are stored in the options container for later use through the library. + + +### Aggregate connections ### + +Aggregate connections are the foundation upon which Predis implements clustering and replication and +they are used to group multiple connections to single Redis nodes and hide the specific logic needed +to handle them properly depending on the context. Aggregate connections usually require an array of +connection parameters when creating a new client instance. + +#### Cluster #### + +By default, when no specific client options are set and an array of connection parameters is passed +to the client's constructor, Predis configures itself to work in clustering mode using a traditional +client-side sharding approach to create a cluster of independent nodes and distribute the keyspace +among them. This approach needs some form of external health monitoring of nodes and requires manual +operations to rebalance the keyspace when changing its configuration by adding or removing nodes: + +```php +$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3']; + +$client = new Predis\Client($parameters); +``` + +Along with Redis 3.0, a new supervised and coordinated type of clustering was introduced in the form +of [redis-cluster](http://redis.io/topics/cluster-tutorial). This kind of approach uses a different +algorithm to distribute the keyspaces, with Redis nodes coordinating themselves by communicating via +a gossip protocol to handle health status, rebalancing, nodes discovery and request redirection. In +order to connect to a cluster managed by redis-cluster, the client requires a list of its nodes (not +necessarily complete since it will automatically discover new nodes if necessary) and the `cluster` +client options set to `redis`: + +```php +$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3']; +$options = ['cluster' => 'redis']; + +$client = new Predis\Client($parameters, $options); +``` + +#### Replication #### + +The client can be configured to operate in a single master / multiple slaves setup to provide better +service availability. When using replication, Predis recognizes read-only commands and sends them to +a random slave in order to provide some sort of load-balancing and switches to the master as soon as +it detects a command that performs any kind of operation that would end up modifying the keyspace or +the value of a key. Instead of raising a connection error when a slave fails, the client attempts to +fall back to a different slave among the ones provided in the configuration. + +The basic configuration needed to use the client in replication mode requires one Redis server to be +identified as the master (this can be done via connection parameters using the `alias` parameter set +to `master`) and one or more servers acting as slaves: + +```php +$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3']; +$options = ['replication' => true]; + +$client = new Predis\Client($parameters, $options); +``` + +The above configuration has a static list of servers and relies entirely on the client's logic, but +it is possible to rely on [`redis-sentinel`](http://redis.io/topics/sentinel) for a more robust HA +environment with sentinel servers acting as a source of authority for clients for service discovery. +The minimum configuration required by the client to work with redis-sentinel is a list of connection +parameters pointing to a bunch of sentinel instances, the `replication` option set to `sentinel` and +the `service` option set to the name of the service: + +```php +$sentinels = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3']; +$options = ['replication' => 'sentinel', 'service' => 'mymaster']; + +$client = new Predis\Client($sentinels, $options); +``` + +If the master and slave nodes are configured to require an authentication from clients, a password +must be provided via the global `parameters` client option. This option can also be used to specify +a different database index. The client options array would then look like this: + +```php +$options = [ + 'replication' => 'sentinel', + 'service' => 'mymaster', + 'parameters' => [ + 'password' => $secretpassword, + 'database' => 10, + ], +]; +``` + +While Predis is able to distinguish commands performing write and read-only operations, `EVAL` and +`EVALSHA` represent a corner case in which the client switches to the master node because it cannot +tell when a Lua script is safe to be executed on slaves. While this is indeed the default behavior, +when certain Lua scripts do not perform write operations it is possible to provide an hint to tell +the client to stick with slaves for their execution: + +```php +$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3']; +$options = ['replication' => function () { + // Set scripts that won't trigger a switch from a slave to the master node. + $strategy = new Predis\Replication\ReplicationStrategy(); + $strategy->setScriptReadOnly($LUA_SCRIPT); + + return new Predis\Connection\Aggregate\MasterSlaveReplication($strategy); +}]; + +$client = new Predis\Client($parameters, $options); +$client->eval($LUA_SCRIPT, 0); // Sticks to slave using `eval`... +$client->evalsha(sha1($LUA_SCRIPT), 0); // ... and `evalsha`, too. +``` + +The [`examples`](examples/) directory contains a few scripts that demonstrate how the client can be +configured and used to leverage replication in both basic and complex scenarios. + + +### Command pipelines ### + +Pipelining can help with performances when many commands need to be sent to a server by reducing the +latency introduced by network round-trip timings. Pipelining also works with aggregate connections. +The client can execute the pipeline inside a callable block or return a pipeline instance with the +ability to chain commands thanks to its fluent interface: + +```php +// Executes a pipeline inside the given callable block: +$responses = $client->pipeline(function ($pipe) { + for ($i = 0; $i < 1000; $i++) { + $pipe->set("key:$i", str_pad($i, 4, '0', 0)); + $pipe->get("key:$i"); + } +}); + +// Returns a pipeline that can be chained thanks to its fluent interface: +$responses = $client->pipeline()->set('foo', 'bar')->get('foo')->execute(); +``` + + +### Transactions ### + +The client provides an abstraction for Redis transactions based on `MULTI` and `EXEC` with a similar +interface to command pipelines: + +```php +// Executes a transaction inside the given callable block: +$responses = $client->transaction(function ($tx) { + $tx->set('foo', 'bar'); + $tx->get('foo'); +}); + +// Returns a transaction that can be chained thanks to its fluent interface: +$responses = $client->transaction()->set('foo', 'bar')->get('foo')->execute(); +``` + +This abstraction can perform check-and-set operations thanks to `WATCH` and `UNWATCH` and provides +automatic retries of transactions aborted by Redis when `WATCH`ed keys are touched. For an example +of a transaction using CAS you can see [the following example](examples/transaction_using_cas.php). + + +### Adding new commands ### + +While we try to update Predis to stay up to date with all the commands available in Redis, you might +prefer to stick with an old version of the library or provide a different way to filter arguments or +parse responses for specific commands. To achieve that, Predis provides the ability to implement new +command classes to define or override commands in the default server profiles used by the client: + +```php +// Define a new command by extending Predis\Command\Command: +class BrandNewRedisCommand extends Predis\Command\Command +{ + public function getId() + { + return 'NEWCMD'; + } +} + +// Inject your command in the current profile: +$client = new Predis\Client(); +$client->getProfile()->defineCommand('newcmd', 'BrandNewRedisCommand'); + +$response = $client->newcmd(); +``` + +There is also a method to send raw commands without filtering their arguments or parsing responses. +Users must provide the list of arguments for the command as an array, following the signatures as +defined by the [Redis documentation for commands](http://redis.io/commands): + +```php +$response = $client->executeRaw(['SET', 'foo', 'bar']); +``` + + +### Script commands ### + +While it is possible to leverage [Lua scripting](http://redis.io/commands/eval) on Redis 2.6+ using +directly [`EVAL`](http://redis.io/commands/eval) and [`EVALSHA`](http://redis.io/commands/evalsha), +Predis offers script commands as an higher level abstraction built upon them to make things simple. +Script commands can be registered in the server profile used by the client and are accessible as if +they were plain Redis commands, but they define Lua scripts that get transmitted to the server for +remote execution. Internally they use [`EVALSHA`](http://redis.io/commands/evalsha) by default and +identify a script by its SHA1 hash to save bandwidth, but [`EVAL`](http://redis.io/commands/eval) +is used as a fall back when needed: + +```php +// Define a new script command by extending Predis\Command\ScriptCommand: +class ListPushRandomValue extends Predis\Command\ScriptCommand +{ + public function getKeysCount() + { + return 1; + } + + public function getScript() + { + return <<getProfile()->defineCommand('lpushrand', 'ListPushRandomValue'); + +$response = $client->lpushrand('random_values', $seed = mt_rand()); +``` + + +### Customizable connection backends ### + +Predis can use different connection backends to connect to Redis. Two of them leverage a third party +extension such as [phpiredis](https://github.com/nrk/phpiredis) resulting in major performance gains +especially when dealing with big multibulk responses. While one is based on PHP streams, the other +is based on socket resources provided by `ext-socket`. Both support TCP/IP and UNIX domain sockets: + +```php +$client = new Predis\Client('tcp://127.0.0.1', [ + 'connections' => [ + 'tcp' => 'Predis\Connection\PhpiredisStreamConnection', // PHP stream resources + 'unix' => 'Predis\Connection\PhpiredisSocketConnection', // ext-socket resources + ], +]); +``` + +Developers can create their own connection classes to support whole new network backends, extend +existing classes or provide completely different implementations. Connection classes must implement +`Predis\Connection\NodeConnectionInterface` or extend `Predis\Connection\AbstractConnection`: + +```php +class MyConnectionClass implements Predis\Connection\NodeConnectionInterface +{ + // Implementation goes here... +} + +// Use MyConnectionClass to handle connections for the `tcp` scheme: +$client = new Predis\Client('tcp://127.0.0.1', [ + 'connections' => ['tcp' => 'MyConnectionClass'], +]); +``` + +For a more in-depth insight on how to create new connection backends you can refer to the actual +implementation of the standard connection classes available in the `Predis\Connection` namespace. + + +## Development ## + + +### Reporting bugs and contributing code ### + +Contributions to Predis are highly appreciated either in the form of pull requests for new features, +bug fixes, or just bug reports. We only ask you to adhere to a [basic set of rules](CONTRIBUTING.md) +before submitting your changes or filing bugs on the issue tracker to make it easier for everyone to +stay consistent while working on the project. + + +### Test suite ### + +__ATTENTION__: Do not ever run the test suite shipped with Predis against instances of Redis running +in production environments or containing data you are interested in! + +Predis has a comprehensive test suite covering every aspect of the library. This test suite performs +integration tests against a running instance of Redis (>= 2.4.0 is required) to verify the correct +behavior of the implementation of each command and automatically skips commands not defined in the +specified Redis profile. If you do not have Redis up and running, integration tests can be disabled. +By default the test suite is configured to execute integration tests using the profile for Redis 3.2 +(which is the current stable version of Redis) but can optionally target a Redis instance built from +the `unstable` branch by modifying `phpunit.xml` and setting `REDIS_SERVER_VERSION` to `dev` so that +the development server profile will be used. You can refer to [the tests README](tests/README.md) +for more detailed information about testing Predis. + +Predis uses Travis CI for continuous integration and the history for past and current builds can be +found [on its project page](http://travis-ci.org/nrk/predis). + + +## Other ## + + +### Project related links ### + +- [Source code](https://github.com/nrk/predis) +- [Wiki](https://wiki.github.com/nrk/predis) +- [Issue tracker](https://github.com/nrk/predis/issues) +- [PEAR channel](http://pear.nrk.io) + + +### Author ### + +- [Daniele Alessandri](mailto:suppakilla@gmail.com) ([twitter](http://twitter.com/JoL1hAHN)) + + +### License ### + +The code for Predis is distributed under the terms of the MIT license (see [LICENSE](LICENSE)). + +[ico-license]: https://img.shields.io/github/license/nrk/predis.svg?style=flat-square +[ico-version-stable]: https://img.shields.io/packagist/v/predis/predis.svg?style=flat-square +[ico-version-dev]: https://img.shields.io/packagist/vpre/predis/predis.svg?style=flat-square +[ico-downloads-monthly]: https://img.shields.io/packagist/dm/predis/predis.svg?style=flat-square +[ico-travis]: https://img.shields.io/travis/nrk/predis.svg?style=flat-square +[ico-hhvm]: https://img.shields.io/hhvm/predis/predis.svg?style=flat-square +[ico-gitter]: https://img.shields.io/gitter/room/nrk/predis.svg?style=flat-square + +[link-packagist]: https://packagist.org/packages/predis/predis +[link-travis]: https://travis-ci.org/nrk/predis +[link-downloads]: https://packagist.org/packages/predis/predis/stats +[link-hhvm]: http://hhvm.h4cc.de/package/predis/predis +[link-gitter]: https://gitter.im/nrk/predis diff --git a/vendor/predis/predis/VERSION b/vendor/predis/predis/VERSION new file mode 100644 index 0000000..524cb55 --- /dev/null +++ b/vendor/predis/predis/VERSION @@ -0,0 +1 @@ +1.1.1 diff --git a/vendor/predis/predis/autoload.php b/vendor/predis/predis/autoload.php new file mode 100644 index 0000000..6b5a00b --- /dev/null +++ b/vendor/predis/predis/autoload.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/src/Autoloader.php'; + +Predis\Autoloader::register(); diff --git a/vendor/predis/predis/bin/create-command-test b/vendor/predis/predis/bin/create-command-test new file mode 100644 index 0000000..930797d --- /dev/null +++ b/vendor/predis/predis/bin/create-command-test @@ -0,0 +1,275 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// -------------------------------------------------------------------------- // +// This script can be used to automatically generate a file with the scheleton +// of a test case to test a Redis command by specifying the name of the class +// in the Predis\Command namespace (only classes in this namespace are valid). +// For example, to generate a test case for SET (which is represented by the +// Predis\Command\StringSet class): +// +// $ ./bin/generate-command-test --class=StringSet +// +// Here is a list of optional arguments: +// +// --realm: each command has its own realm (commands that operate on strings, +// lists, sets and such) but while this realm is usually inferred from the name +// of the specified class, sometimes it can be useful to override it with a +// custom one. +// +// --output: write the generated test case to the specified path instead of +// the default one. +// +// --overwrite: pre-existing test files are not overwritten unless this option +// is explicitly specified. +// -------------------------------------------------------------------------- // + +use Predis\Command\CommandInterface; +use Predis\Command\PrefixableCommandInterface; + +class CommandTestCaseGenerator +{ + private $options; + + public function __construct(array $options) + { + if (!isset($options['class'])) { + throw new RuntimeException("Missing 'class' option."); + } + $this->options = $options; + } + + public static function fromCommandLine() + { + $parameters = array( + 'c:' => 'class:', + 'r::' => 'realm::', + 'o::' => 'output::', + 'x::' => 'overwrite::' + ); + + $getops = getopt(implode(array_keys($parameters)), $parameters); + + $options = array( + 'overwrite' => false, + 'tests' => __DIR__.'/../tests/Predis', + ); + + foreach ($getops as $option => $value) { + switch ($option) { + case 'c': + case 'class': + $options['class'] = $value; + break; + + case 'r': + case 'realm': + $options['realm'] = $value; + break; + + case 'o': + case 'output': + $options['output'] = $value; + break; + + case 'x': + case 'overwrite': + $options['overwrite'] = true; + break; + } + } + + if (!isset($options['class'])) { + throw new RuntimeException("Missing 'class' option."); + } + + $options['fqn'] = "Predis\\Command\\{$options['class']}"; + $options['path'] = "Command/{$options['class']}.php"; + + $source = __DIR__.'/../src/'.$options['path']; + if (!file_exists($source)) { + throw new RuntimeException("Cannot find class file for {$options['fqn']} in $source."); + } + + if (!isset($options['output'])) { + $options['output'] = sprintf("%s/%s", $options['tests'], str_replace('.php', 'Test.php', $options['path'])); + } + + return new self($options); + } + + protected function getTestRealm() + { + if (isset($this->options['realm'])) { + if (!$this->options['realm']) { + throw new RuntimeException('Invalid value for realm has been sepcified (empty).'); + } + return $this->options['realm']; + } + + $fqnParts = explode('\\', $this->options['fqn']); + $class = array_pop($fqnParts); + list($realm,) = preg_split('/([[:upper:]][[:lower:]]+)/', $class, 2, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + + return strtolower($realm); + } + + public function generate() + { + $reflection = new ReflectionClass($class = $this->options['fqn']); + + if (!$reflection->isInstantiable()) { + throw new RuntimeException("Class $class must be instantiable, abstract classes or interfaces are not allowed."); + } + if (!$reflection->implementsInterface('Predis\Command\CommandInterface')) { + throw new RuntimeException("Class $class must implement Predis\Command\CommandInterface."); + } + + /* + * @var CommandInterface + */ + $instance = $reflection->newInstance(); + + $buffer = $this->getTestCaseBuffer($instance); + + return $buffer; + } + + public function save() + { + $options = $this->options; + if (file_exists($options['output']) && !$options['overwrite']) { + throw new RuntimeException("File {$options['output']} already exist. Specify the --overwrite option to overwrite the existing file."); + } + file_put_contents($options['output'], $this->generate()); + } + + protected function getTestCaseBuffer(CommandInterface $instance) + { + $id = $instance->getId(); + $fqn = get_class($instance); + $fqnParts = explode('\\', $fqn); + $class = array_pop($fqnParts) . "Test"; + $realm = $this->getTestRealm(); + + $buffer =<< + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @group commands + * @group realm-$realm + */ +class $class extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand() + { + return '$fqn'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId() + { + return '$id'; + } + + /** + * @group disconnected + */ + public function testFilterArguments() + { + \$this->markTestIncomplete('This test has not been implemented yet.'); + + \$arguments = array(/* add arguments */); + \$expected = array(/* add arguments */); + + \$command = \$this->getCommand(); + \$command->setArguments(\$arguments); + + \$this->assertSame(\$expected, \$command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse() + { + \$this->markTestIncomplete('This test has not been implemented yet.'); + + \$raw = null; + \$expected = null; + + \$command = \$this->getCommand(); + + \$this->assertSame(\$expected, \$command->parseResponse(\$raw)); + } + +PHP; + + if ($instance instanceof PrefixableCommandInterface) { + $buffer .=<<markTestIncomplete('This test has not been implemented yet.'); + + \$arguments = array(/* add arguments */); + \$expected = array(/* add arguments */); + + \$command = \$this->getCommandWithArgumentsArray(\$arguments); + \$command->prefixKeys('prefix:'); + + \$this->assertSame(\$expected, \$command->getArguments()); + } + + /** + * @group disconnected + */ + public function testPrefixKeysIgnoredOnEmptyArguments() + { + \$command = \$this->getCommand(); + \$command->prefixKeys('prefix:'); + + \$this->assertSame(array(), \$command->getArguments()); + } + +PHP; + } + + return "$buffer}\n"; + } +} + +// ------------------------------------------------------------------------- // + +require __DIR__.'/../autoload.php'; + +$generator = CommandTestCaseGenerator::fromCommandLine(); +$generator->save(); diff --git a/vendor/predis/predis/bin/create-pear b/vendor/predis/predis/bin/create-pear new file mode 100644 index 0000000..b4f92db --- /dev/null +++ b/vendor/predis/predis/bin/create-pear @@ -0,0 +1,233 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// -------------------------------------------------------------------------- // +// In order to be able to execute this script to create a PEAR package of Predis +// the `pear` binary must be available and executable in your $PATH. +// The parts used to parse author and version strings are taken from Onion (used +// by this library in the past) just to keep on relying on the package.ini file +// to simplify things. We might consider to switch to using the PEAR classes +// directly in the future. +// -------------------------------------------------------------------------- // + +function executeWithBackup($file, $callback) +{ + $exception = null; + $backup = "$file.backup"; + + copy($file, $backup); + + try { + call_user_func($callback, $file); + } catch (Exception $exception) { + // NOOP + } + + unlink($file); + rename($backup, $file); + + if ($exception) { + throw $exception; + } +} + +function parseAuthor($string) +{ + $author = array(); + + if (preg_match('/^\s*(.+?)\s*(?:"(\S+)"\s*)?<(\S+)>\s*$/x', $string , $regs)) { + if (count($regs) == 4) { + list($_,$name,$user,$email) = $regs; + $author['name'] = $name; + $author['user'] = $user; + $author['email'] = $email; + } elseif (count($regs) == 3) { + list($_,$name,$email) = $regs; + $author['name'] = $name; + $author['email'] = $email; + } + } else { + $author['name'] = $string; + } + + return $author; +} + +function parseVersion($string) +{ + $version_pattern = '([0-9.]+)'; + + if (preg_match("/^\s*$version_pattern\s*\$/x", $string, $regs)) { + return array('min' => $regs[1] ?: '0.0.0'); + } elseif (preg_match("/^\s*[>=]+\s*$version_pattern\s*\$/x", $string, $regs)) { + return array('min' => $regs[1] ?: '0.0.0'); + } elseif (preg_match("/^\s*[<=]+\s*$version_pattern\s*\$/x", $string, $regs)) { + return array('max' => $regs[1]); + } elseif (preg_match("/^\s*$version_pattern\s*<=>\s*$version_pattern\s*\$/x", $string, $regs)) { + return array( + 'min' => $regs[1] ?: '0.0.0', + 'max' => $regs[2], + ); + } + + return null; +} + +function addRolePath($pkg, $path, $role) +{ + if (is_dir($path)) { + $dirRoot = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); + $dirTree = new RecursiveIteratorIterator($dirRoot, RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($dirTree as $fileinfo) { + if ($fileinfo->isFile()) { + addPackageFile($pkg, $fileinfo, $role, $path); + } + } + } else { + foreach (glob($path) as $filename) { + addPackageFile($pkg, new SplFileInfo($filename), $role); + } + } +} + +function addPackageFile($pkg, $fileinfo, $role, $baseDir = '') +{ + $fileNode = $pkg->contents->dir->addChild('file'); + $fileNode->addAttribute('name', $filepath = $fileinfo->getPathname()); + $fileNode->addAttribute('role', $role); + $fileNode->addAttribute('md5sum', md5_file($filepath)); + + $installNode = $pkg->phprelease->filelist->addChild('install'); + $installNode->addAttribute('name', $filepath); + $installNode->addAttribute('as', !$baseDir ? basename($filepath) : substr($filepath, strlen($baseDir) + 1)); +} + +function generatePackageXml($packageINI) +{ + $XML = << + +XML; + + $cfg = parse_ini_file($packageINI, true); + $pkg = new SimpleXMLElement($XML); + + $pkg->name = $cfg['package']['name']; + $pkg->channel = $cfg['package']['channel']; + $pkg->summary = $cfg['package']['desc']; + $pkg->description = $cfg['package']['desc']; + + $author = parseAuthor($cfg['package']['author']); + $pkg->addChild('lead'); + $pkg->lead->name = $author['name']; + $pkg->lead->user = $author['user']; + $pkg->lead->email = $author['email']; + $pkg->lead->active = 'yes'; + + $datetime = new DateTime('now'); + $pkg->date = $datetime->format('Y-m-d'); + $pkg->time = $datetime->format('H:i:s'); + + $pkg->addChild('version'); + $pkg->version->release = $cfg['package']['version']; + $pkg->version->api = $cfg['package']['version']; + + $pkg->addChild('stability'); + $pkg->stability->release = $cfg['package']['stability']; + $pkg->stability->api = $cfg['package']['stability']; + + $pkg->license = $cfg['package']['license']; + $pkg->notes = '-'; + + $pkg->addChild('contents')->addChild('dir')->addAttribute('name', '/'); + + $pkg->addChild('dependencies')->addChild('required'); + foreach ($cfg['require'] as $required => $version) { + $version = parseVersion($version); + $pkg->dependencies->required->addChild($required); + + if (isset($version['min'])) { + $pkg->dependencies->required->$required->min = $version['min']; + } + if (isset($version['max'])) { + $pkg->dependencies->required->$required->min = $version['max']; + } + } + + $pkg->addChild('phprelease')->addChild('filelist'); + + $pathToRole = array( + 'doc' => 'doc', 'docs' => 'doc', 'examples' => 'doc', + 'lib' => 'php', 'src' => 'php', + 'test' => 'test', 'tests' => 'test', + ); + + foreach (array_merge($pathToRole, $cfg['roles'] ?: array()) as $path => $role) { + addRolePath($pkg, $path, $role); + } + + return $pkg; +} + +function rewritePackageInstallAs($pkg) +{ + foreach ($pkg->phprelease->filelist->install as $file) { + if (preg_match('/^src\//', $file['name'])) { + $file['as'] = "Predis/{$file['as']}"; + } + } +} + +function savePackageXml($xml) +{ + $dom = new DOMDocument("1.0"); + $dom->preserveWhiteSpace = false; + $dom->formatOutput = true; + $dom->loadXML($xml->asXML()); + + file_put_contents('package.xml', $dom->saveXML()); +} + +function buildPackage() +{ + passthru('pear -q package && rm package.xml'); +} + +function modifyPhpunitXml($file) +{ + $cfg = new SimpleXMLElement($file, null, true); + + $cfg[0]['bootstrap'] = str_replace('tests/', '', $cfg[0]['bootstrap']); + $cfg->testsuites->testsuite->directory = str_replace('tests/', '', $cfg->testsuites->testsuite->directory); + + $cfg->saveXml($file); +} + +// -------------------------------------------------------------------------- // + +executeWithBackup(__DIR__.'/../phpunit.xml.dist', function ($file) { + modifyPhpunitXml($file); + + $pkg = generatePackageXml('package.ini'); + rewritePackageInstallAs($pkg); + savePackageXml($pkg); + + buildPackage(); +}); diff --git a/vendor/predis/predis/bin/create-phar b/vendor/predis/predis/bin/create-phar new file mode 100644 index 0000000..8501d4a --- /dev/null +++ b/vendor/predis/predis/bin/create-phar @@ -0,0 +1,71 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// -------------------------------------------------------------------------- // +// In order to be able to execute this script to create a Phar archive of Predis, +// the Phar module must be loaded and the "phar.readonly" directive php.ini must +// be set to "off". You can change the values in the $options array to customize +// the creation of the Phar archive to better suit your needs. +// -------------------------------------------------------------------------- // + +$options = array( + 'name' => 'predis', + 'project_path' => __DIR__ . '/../src', + 'compression' => Phar::NONE, + 'append_version' => true, +); + +function getPharFilename($options) +{ + $filename = $options['name']; + + // NOTE: do not consider "append_version" with Phar compression do to a bug in + // Phar::compress() when renaming phar archives containing dots in their name. + if ($options['append_version'] && $options['compression'] === Phar::NONE) { + $versionFile = @fopen(__DIR__ . '/../VERSION', 'r'); + + if ($versionFile === false) { + throw new Exception("Could not locate the VERSION file."); + } + + $version = trim(fgets($versionFile)); + fclose($versionFile); + $filename .= "_$version"; + } + + return "$filename.phar"; +} + +function getPharStub($options) +{ + return <<compress($options['compression']); +$phar->setStub(getPharStub($options)); +$phar->buildFromDirectory($options['project_path']); diff --git a/vendor/predis/predis/bin/create-single-file b/vendor/predis/predis/bin/create-single-file new file mode 100644 index 0000000..ecb876b --- /dev/null +++ b/vendor/predis/predis/bin/create-single-file @@ -0,0 +1,662 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// -------------------------------------------------------------------------- // +// This script can be used to automatically glue all the .php files of Predis +// into a single monolithic script file that can be used without an autoloader, +// just like the other previous versions of the library. +// +// Much of its complexity is due to the fact that we cannot simply join PHP +// files, but namespaces and classes definitions must follow a precise order +// when dealing with subclassing and inheritance. +// +// The current implementation is pretty naïve, but it should do for now. +// -------------------------------------------------------------------------- // + +class CommandLine +{ + public static function getOptions() + { + $parameters = array( + 's:' => 'source:', + 'o:' => 'output:', + 'e:' => 'exclude:', + 'E:' => 'exclude-classes:', + ); + + $getops = getopt(implode(array_keys($parameters)), $parameters); + + $options = array( + 'source' => __DIR__ . "/../src", + 'output' => PredisFile::NS_ROOT . '.php', + 'exclude' => array(), + ); + + foreach ($getops as $option => $value) { + switch ($option) { + case 's': + case 'source': + $options['source'] = $value; + break; + + case 'o': + case 'output': + $options['output'] = $value; + break; + + case 'E': + case 'exclude-classes': + $options['exclude'] = @file($value, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: $value; + break; + + case 'e': + case 'exclude': + $options['exclude'] = is_array($value) ? $value : array($value); + break; + } + } + + return $options; + } +} + +class PredisFile +{ + const NS_ROOT = 'Predis'; + + private $namespaces; + + public function __construct() + { + $this->namespaces = array(); + } + + public static function from($libraryPath, array $exclude = array()) + { + $predisFile = new PredisFile(); + $libIterator = new RecursiveDirectoryIterator($libraryPath); + + foreach (new RecursiveIteratorIterator($libIterator) as $classFile) + { + if (!$classFile->isFile()) { + continue; + } + + $namespace = self::NS_ROOT.strtr(str_replace($libraryPath, '', $classFile->getPath()), '/', '\\'); + + if (in_array(sprintf('%s\\%s', $namespace, $classFile->getBasename('.php')), $exclude)) { + continue; + } + + $phpNamespace = $predisFile->getNamespace($namespace); + + if ($phpNamespace === false) { + $phpNamespace = new PhpNamespace($namespace); + $predisFile->addNamespace($phpNamespace); + } + + $phpClass = new PhpClass($phpNamespace, $classFile); + } + + return $predisFile; + } + + public function addNamespace(PhpNamespace $namespace) + { + if (isset($this->namespaces[(string)$namespace])) { + throw new InvalidArgumentException("Duplicated namespace"); + } + $this->namespaces[(string)$namespace] = $namespace; + } + + public function getNamespaces() + { + return $this->namespaces; + } + + public function getNamespace($namespace) + { + if (!isset($this->namespaces[$namespace])) { + return false; + } + + return $this->namespaces[$namespace]; + } + + public function getClassByFQN($classFqn) + { + if (($nsLastPos = strrpos($classFqn, '\\')) !== false) { + $namespace = $this->getNamespace(substr($classFqn, 0, $nsLastPos)); + if ($namespace === false) { + return null; + } + $className = substr($classFqn, $nsLastPos + 1); + + return $namespace->getClass($className); + } + + return null; + } + + private function calculateDependencyScores(&$classes, $fqn) + { + if (!isset($classes[$fqn])) { + $classes[$fqn] = 0; + } + + $classes[$fqn] += 1; + + if (($phpClass = $this->getClassByFQN($fqn)) === null) { + throw new RuntimeException( + "Cannot found the class $fqn which is required by other subclasses. Are you missing a file?" + ); + } + + foreach ($phpClass->getDependencies() as $fqn) { + $this->calculateDependencyScores($classes, $fqn); + } + } + + private function getDependencyScores() + { + $classes = array(); + + foreach ($this->getNamespaces() as $phpNamespace) { + foreach ($phpNamespace->getClasses() as $phpClass) { + $this->calculateDependencyScores($classes, $phpClass->getFQN()); + } + } + + return $classes; + } + + private function getOrderedNamespaces($dependencyScores) + { + $namespaces = array_fill_keys(array_unique( + array_map( + function ($fqn) { return PhpNamespace::extractName($fqn); }, + array_keys($dependencyScores) + ) + ), 0); + + foreach ($dependencyScores as $classFqn => $score) { + $namespaces[PhpNamespace::extractName($classFqn)] += $score; + } + + arsort($namespaces); + + return array_keys($namespaces); + } + + private function getOrderedClasses(PhpNamespace $phpNamespace, $classes) + { + $nsClassesFQNs = array_map(function ($cl) { return $cl->getFQN(); }, $phpNamespace->getClasses()); + $nsOrderedClasses = array(); + + foreach ($nsClassesFQNs as $nsClassFQN) { + $nsOrderedClasses[$nsClassFQN] = $classes[$nsClassFQN]; + } + + arsort($nsOrderedClasses); + + return array_keys($nsOrderedClasses); + } + + public function getPhpCode() + { + $buffer = array("getDependencyScores(); + $namespaces = $this->getOrderedNamespaces($classes); + + foreach ($namespaces as $namespace) { + $phpNamespace = $this->getNamespace($namespace); + + // generate namespace directive + $buffer[] = $phpNamespace->getPhpCode(); + $buffer[] = "\n"; + + // generate use directives + $useDirectives = $phpNamespace->getUseDirectives(); + if (count($useDirectives) > 0) { + $buffer[] = $useDirectives->getPhpCode(); + $buffer[] = "\n"; + } + + // generate classes bodies + $nsClasses = $this->getOrderedClasses($phpNamespace, $classes); + foreach ($nsClasses as $classFQN) { + $buffer[] = $this->getClassByFQN($classFQN)->getPhpCode(); + $buffer[] = "\n\n"; + } + + $buffer[] = "/* " . str_repeat("-", 75) . " */"; + $buffer[] = "\n\n"; + } + + return implode($buffer); + } + + public function saveTo($outputFile) + { + // TODO: add more sanity checks + if ($outputFile === null || $outputFile === '') { + throw new InvalidArgumentException('You must specify a valid output file'); + } + file_put_contents($outputFile, $this->getPhpCode()); + } +} + +class PhpNamespace implements IteratorAggregate +{ + private $namespace; + private $classes; + + public function __construct($namespace) + { + $this->namespace = $namespace; + $this->classes = array(); + $this->useDirectives = new PhpUseDirectives($this); + } + + public static function extractName($fqn) + { + $nsSepLast = strrpos($fqn, '\\'); + if ($nsSepLast === false) { + return $fqn; + } + $ns = substr($fqn, 0, $nsSepLast); + + return $ns !== '' ? $ns : null; + } + + public function addClass(PhpClass $class) + { + $this->classes[$class->getName()] = $class; + } + + public function getClass($className) + { + if (isset($this->classes[$className])) { + return $this->classes[$className]; + } + } + + public function getClasses() + { + return array_values($this->classes); + } + + public function getIterator() + { + return new \ArrayIterator($this->getClasses()); + } + + public function getUseDirectives() + { + return $this->useDirectives; + } + + public function getPhpCode() + { + return "namespace $this->namespace;\n"; + } + + public function __toString() + { + return $this->namespace; + } +} + +class PhpUseDirectives implements Countable, IteratorAggregate +{ + private $use; + private $aliases; + private $reverseAliases; + private $namespace; + + public function __construct(PhpNamespace $namespace) + { + $this->namespace = $namespace; + $this->use = array(); + $this->aliases = array(); + $this->reverseAliases = array(); + } + + public function add($use, $as = null) + { + if (in_array($use, $this->use)) { + return; + } + + $rename = null; + $this->use[] = $use; + $aliasedClassName = $as ?: PhpClass::extractName($use); + + if (isset($this->aliases[$aliasedClassName])) { + $parentNs = $this->getParentNamespace(); + + if ($parentNs && false !== $pos = strrpos($parentNs, '\\')) { + $parentNs = substr($parentNs, $pos); + } + + $newAlias = "{$parentNs}_{$aliasedClassName}"; + $rename = (object) array( + 'namespace' => $this->namespace, + 'from' => $aliasedClassName, + 'to' => $newAlias, + ); + + $this->aliases[$newAlias] = $use; + $as = $newAlias; + } else { + $this->aliases[$aliasedClassName] = $use; + } + + if ($as !== null) { + $this->reverseAliases[$use] = $as; + } + + return $rename; + } + + public function getList() + { + return $this->use; + } + + public function getIterator() + { + return new \ArrayIterator($this->getList()); + } + + public function getPhpCode() + { + $reverseAliases = $this->reverseAliases; + + $reducer = function ($str, $use) use ($reverseAliases) { + if (isset($reverseAliases[$use])) { + return $str .= "use $use as {$reverseAliases[$use]};\n"; + } else { + return $str .= "use $use;\n"; + } + }; + + return array_reduce($this->getList(), $reducer, ''); + } + + public function getNamespace() + { + return $this->namespace; + } + + public function getParentNamespace() + { + if (false !== $pos = strrpos($this->namespace, '\\')) { + return substr($this->namespace, 0, $pos); + } + + return ''; + } + + public function getFQN($className) + { + if (($nsSepFirst = strpos($className, '\\')) === false) { + if (isset($this->aliases[$className])) { + return $this->aliases[$className]; + } + + return (string)$this->getNamespace() . "\\$className"; + } + + if ($nsSepFirst != 0) { + throw new InvalidArgumentException("Partially qualified names are not supported"); + } + + return $className; + } + + public function count() + { + return count($this->use); + } +} + +class PhpClass +{ + const LICENSE_HEADER = << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +LICENSE; + + private $namespace; + private $file; + private $body; + private $implements; + private $extends; + private $name; + + public function __construct(PhpNamespace $namespace, SplFileInfo $classFile) + { + $this->namespace = $namespace; + $this->file = $classFile; + $this->implements = array(); + $this->extends = array(); + + $this->extractData(); + $namespace->addClass($this); + } + + public static function extractName($fqn) + { + $nsSepLast = strrpos($fqn, '\\'); + if ($nsSepLast === false) { + return $fqn; + } + + return substr($fqn, $nsSepLast + 1); + } + + private function extractData() + { + $renames = array(); + $useDirectives = $this->getNamespace()->getUseDirectives(); + + $useExtractor = function ($m) use ($useDirectives, &$renames) { + array_shift($m); + + if (isset($m[1])) { + $m[1] = str_replace(" as ", '', $m[1]); + } + + if ($rename = call_user_func_array(array($useDirectives, 'add'), $m)) { + $renames[] = $rename; + } + }; + + $classBuffer = stream_get_contents(fopen($this->getFile()->getPathname(), 'r')); + + $classBuffer = str_replace(self::LICENSE_HEADER, '', $classBuffer); + + $classBuffer = preg_replace('/<\?php\s?\\n\s?/', '', $classBuffer); + $classBuffer = preg_replace('/\s?\?>\n?/ms', '', $classBuffer); + $classBuffer = preg_replace('/namespace\s+[\w\d_\\\\]+;\s?/', '', $classBuffer); + $classBuffer = preg_replace_callback('/use\s+([\w\d_\\\\]+)(\s+as\s+.*)?;\s?\n?/', $useExtractor, $classBuffer); + + foreach ($renames as $rename) { + $classBuffer = str_replace($rename->from, $rename->to, $classBuffer); + } + + $this->body = trim($classBuffer); + + $this->extractHierarchy(); + } + + private function extractHierarchy() + { + $implements = array(); + $extends = array(); + + $extractor = function ($iterator, $callback) { + $className = ''; + $iterator->seek($iterator->key() + 1); + + while ($iterator->valid()) { + $token = $iterator->current(); + + if (is_string($token)) { + if (preg_match('/\s?,\s?/', $token)) { + $callback(trim($className)); + $className = ''; + } else if ($token == '{') { + $callback(trim($className)); + return; + } + } + + switch ($token[0]) { + case T_NS_SEPARATOR: + $className .= '\\'; + break; + + case T_STRING: + $className .= $token[1]; + break; + + case T_IMPLEMENTS: + case T_EXTENDS: + $callback(trim($className)); + $iterator->seek($iterator->key() - 1); + return; + } + + $iterator->next(); + } + }; + + $tokens = token_get_all("getPhpCode())); + $iterator = new ArrayIterator($tokens); + + while ($iterator->valid()) { + $token = $iterator->current(); + if (is_string($token)) { + $iterator->next(); + continue; + } + + switch ($token[0]) { + case T_CLASS: + case T_INTERFACE: + $iterator->seek($iterator->key() + 2); + $tk = $iterator->current(); + $this->name = $tk[1]; + break; + + case T_IMPLEMENTS: + $extractor($iterator, function ($fqn) use (&$implements) { + $implements[] = $fqn; + }); + break; + + case T_EXTENDS: + $extractor($iterator, function ($fqn) use (&$extends) { + $extends[] = $fqn; + }); + break; + } + + $iterator->next(); + } + + $this->implements = $this->guessFQN($implements); + $this->extends = $this->guessFQN($extends); + } + + public function guessFQN($classes) + { + $useDirectives = $this->getNamespace()->getUseDirectives(); + return array_map(array($useDirectives, 'getFQN'), $classes); + } + + public function getImplementedInterfaces($all = false) + { + if ($all) { + return $this->implements; + } + + return array_filter( + $this->implements, + function ($cn) { return strpos($cn, 'Predis\\') === 0; } + ); + } + + public function getExtendedClasses($all = false) + { + if ($all) { + return $this->extemds; + } + + return array_filter( + $this->extends, + function ($cn) { return strpos($cn, 'Predis\\') === 0; } + ); + } + + public function getDependencies($all = false) + { + return array_merge( + $this->getImplementedInterfaces($all), + $this->getExtendedClasses($all) + ); + } + + public function getNamespace() + { + return $this->namespace; + } + + public function getFile() + { + return $this->file; + } + + public function getName() + { + return $this->name; + } + + public function getFQN() + { + return (string)$this->getNamespace() . '\\' . $this->name; + } + + public function getPhpCode() + { + return $this->body; + } + + public function __toString() + { + return "class " . $this->getName() . '{ ... }'; + } +} + +/* -------------------------------------------------------------------------- */ + +$options = CommandLine::getOptions(); +$predisFile = PredisFile::from($options['source'], $options['exclude']); +$predisFile->saveTo($options['output']); diff --git a/vendor/predis/predis/composer.json b/vendor/predis/predis/composer.json new file mode 100644 index 0000000..bde6c74 --- /dev/null +++ b/vendor/predis/predis/composer.json @@ -0,0 +1,31 @@ +{ + "name": "predis/predis", + "type": "library", + "description": "Flexible and feature-complete Redis client for PHP and HHVM", + "keywords": ["nosql", "redis", "predis"], + "homepage": "http://github.com/nrk/predis", + "license": "MIT", + "support": { + "issues": "https://github.com/nrk/predis/issues" + }, + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net" + } + ], + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol", + "ext-curl": "Allows access to Webdis when paired with phpiredis" + }, + "autoload": { + "psr-4": {"Predis\\": "src/"} + } +} diff --git a/vendor/predis/predis/examples/custom_cluster_distributor.php b/vendor/predis/predis/examples/custom_cluster_distributor.php new file mode 100644 index 0000000..0a3a421 --- /dev/null +++ b/vendor/predis/predis/examples/custom_cluster_distributor.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// Developers can implement Predis\Distribution\DistributorInterface to create +// their own distributors used by the client to distribute keys among a cluster +// of servers. + +use Predis\Cluster\Distributor\DistributorInterface; +use Predis\Cluster\Hash\HashGeneratorInterface; +use Predis\Cluster\PredisStrategy; +use Predis\Connection\Aggregate\PredisCluster; + +class NaiveDistributor implements DistributorInterface, HashGeneratorInterface +{ + private $nodes; + private $nodesCount; + + public function __construct() + { + $this->nodes = array(); + $this->nodesCount = 0; + } + + public function add($node, $weight = null) + { + $this->nodes[] = $node; + ++$this->nodesCount; + } + + public function remove($node) + { + $this->nodes = array_filter($this->nodes, function ($n) use ($node) { + return $n !== $node; + }); + + $this->nodesCount = count($this->nodes); + } + + public function getSlot($hash) + { + return $this->nodesCount > 1 ? abs($hash % $this->nodesCount) : 0; + } + + public function getBySlot($slot) + { + return isset($this->nodes[$slot]) ? $this->nodes[$slot] : null; + } + + public function getByHash($hash) + { + if (!$this->nodesCount) { + throw new RuntimeException('No connections.'); + } + + $slot = $this->getSlot($hash); + $node = $this->getBySlot($slot); + + return $node; + } + + public function get($value) + { + $hash = $this->hash($value); + $node = $this->getByHash($hash); + + return $node; + } + + public function hash($value) + { + return crc32($value); + } + + public function getHashGenerator() + { + return $this; + } +} + +$options = array( + 'cluster' => function () { + $distributor = new NaiveDistributor(); + $strategy = new PredisStrategy($distributor); + $cluster = new PredisCluster($strategy); + + return $cluster; + }, +); + +$client = new Predis\Client($multiple_servers, $options); + +for ($i = 0; $i < 100; ++$i) { + $client->set("key:$i", str_pad($i, 4, '0', 0)); + $client->get("key:$i"); +} + +$server1 = $client->getClientFor('first')->info(); +$server2 = $client->getClientFor('second')->info(); + +if (isset($server1['Keyspace'], $server2['Keyspace'])) { + $server1 = $server1['Keyspace']; + $server2 = $server2['Keyspace']; +} + +printf("Server '%s' has %d keys while server '%s' has %d keys.\n", + 'first', $server1['db15']['keys'], 'second', $server2['db15']['keys'] +); diff --git a/vendor/predis/predis/examples/debuggable_connection.php b/vendor/predis/predis/examples/debuggable_connection.php new file mode 100644 index 0000000..346b003 --- /dev/null +++ b/vendor/predis/predis/examples/debuggable_connection.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// This is an example of how you can easily extend an existing connection class +// and trace the execution of commands for debugging purposes. This can be quite +// useful as a starting poing to understand how your application interacts with +// Redis. + +use Predis\Command\CommandInterface; +use Predis\Connection\StreamConnection; + +class SimpleDebuggableConnection extends StreamConnection +{ + private $tstart = 0; + private $debugBuffer = array(); + + public function connect() + { + $this->tstart = microtime(true); + + parent::connect(); + } + + private function storeDebug(CommandInterface $command, $direction) + { + $firtsArg = $command->getArgument(0); + $timestamp = round(microtime(true) - $this->tstart, 4); + + $debug = $command->getId(); + $debug .= isset($firtsArg) ? " $firtsArg " : ' '; + $debug .= "$direction $this"; + $debug .= " [{$timestamp}s]"; + + $this->debugBuffer[] = $debug; + } + + public function writeRequest(CommandInterface $command) + { + parent::writeRequest($command); + + $this->storeDebug($command, '->'); + } + + public function readResponse(CommandInterface $command) + { + $response = parent::readResponse($command); + $this->storeDebug($command, '<-'); + + return $response; + } + + public function getDebugBuffer() + { + return $this->debugBuffer; + } +} + +$options = array( + 'connections' => array( + 'tcp' => 'SimpleDebuggableConnection', + ), +); + +$client = new Predis\Client($single_server, $options); +$client->set('foo', 'bar'); +$client->get('foo'); +$client->info(); + +var_export($client->getConnection()->getDebugBuffer()); + +/* OUTPUT: +array ( + 0 => 'SELECT 15 -> 127.0.0.1:6379 [0.0008s]', + 1 => 'SELECT 15 <- 127.0.0.1:6379 [0.001s]', + 2 => 'SET foo -> 127.0.0.1:6379 [0.001s]', + 3 => 'SET foo <- 127.0.0.1:6379 [0.0011s]', + 4 => 'GET foo -> 127.0.0.1:6379 [0.0013s]', + 5 => 'GET foo <- 127.0.0.1:6379 [0.0015s]', + 6 => 'INFO -> 127.0.0.1:6379 [0.0019s]', + 7 => 'INFO <- 127.0.0.1:6379 [0.0022s]', +) +*/ diff --git a/vendor/predis/predis/examples/dispatcher_loop.php b/vendor/predis/predis/examples/dispatcher_loop.php new file mode 100644 index 0000000..7123491 --- /dev/null +++ b/vendor/predis/predis/examples/dispatcher_loop.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// This is a basic example on how to use the Predis\DispatcherLoop class. +// +// To see this example in action you can just use redis-cli and publish some +// messages to the 'events' and 'control' channel, e.g.: + +// ./redis-cli +// PUBLISH events first +// PUBLISH events second +// PUBLISH events third +// PUBLISH control terminate_dispatcher + +// Create a client and disable r/w timeout on the socket +$client = new Predis\Client($single_server + array('read_write_timeout' => 0)); + +// Return an initialized PubSub consumer instance from the client. +$pubsub = $client->pubSubLoop(); + +// Create a dispatcher loop instance and attach a bunch of callbacks. +$dispatcher = new Predis\PubSub\DispatcherLoop($pubsub); + +// Demonstrate how to use a callable class as a callback for the dispatcher loop. +class EventsListener implements Countable +{ + private $events; + + public function __construct() + { + $this->events = array(); + } + + public function count() + { + return count($this->events); + } + + public function getEvents() + { + return $this->events; + } + + public function __invoke($payload) + { + $this->events[] = $payload; + } +} + +// Attach our callable class to the dispatcher. +$dispatcher->attachCallback('events', ($events = new EventsListener())); + +// Attach a function to control the dispatcher loop termination with a message. +$dispatcher->attachCallback('control', function ($payload) use ($dispatcher) { + if ($payload === 'terminate_dispatcher') { + $dispatcher->stop(); + } +}); + +// Run the dispatcher loop until the callback attached to the 'control' channel +// receives 'terminate_dispatcher' as a message. +$dispatcher->run(); + +// Display our achievements! +echo "We received {$events->count()} messages!", PHP_EOL; + +// Say goodbye :-) +$version = redis_version($client->info()); +echo "Goodbye from Redis $version!", PHP_EOL; diff --git a/vendor/predis/predis/examples/executing_redis_commands.php b/vendor/predis/predis/examples/executing_redis_commands.php new file mode 100644 index 0000000..6e56753 --- /dev/null +++ b/vendor/predis/predis/examples/executing_redis_commands.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +$client = new Predis\Client($single_server); + +// Plain old SET and GET example... +$client->set('library', 'predis'); +$response = $client->get('library'); + +var_export($response); echo PHP_EOL; +/* OUTPUT: 'predis' */ + +// Redis has the MSET and MGET commands to set or get multiple keys in one go, +// cases like this Predis accepts arguments for variadic commands both as a list +// of arguments or an array containing all of the keys and/or values. +$mkv = array( + 'uid:0001' => '1st user', + 'uid:0002' => '2nd user', + 'uid:0003' => '3rd user', +); + +$client->mset($mkv); +$response = $client->mget(array_keys($mkv)); + +var_export($response); echo PHP_EOL; +/* OUTPUT: +array ( + 0 => '1st user', + 1 => '2nd user', + 2 => '3rd user', +) */ + +// Predis can also send "raw" commands to Redis. The difference between sending +// commands to Redis the usual way and the "raw" way is that in the latter case +// their arguments are not filtered nor responses coming from Redis are parsed. + +$response = $client->executeRaw(array( + 'MGET', 'uid:0001', 'uid:0002', 'uid:0003', +)); + +var_export($response); echo PHP_EOL; +/* OUTPUT: +array ( + 0 => '1st user', + 1 => '2nd user', + 2 => '3rd user', +) */ diff --git a/vendor/predis/predis/examples/key_prefixing.php b/vendor/predis/predis/examples/key_prefixing.php new file mode 100644 index 0000000..1486330 --- /dev/null +++ b/vendor/predis/predis/examples/key_prefixing.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// Predis can prefix keys found in commands arguments before sending commands to +// Redis, even for complex commands such as SORT, ZUNIONSTORE and ZINTERSTORE. +// Prefixing keys can be useful to create user-level namespaces for you keyspace +// thus reducing the need for separate logical databases in certain scenarios. + +$client = new Predis\Client($single_server, array('prefix' => 'nrk:')); + +$client->mset(array('foo' => 'bar', 'lol' => 'wut')); +var_export($client->mget('foo', 'lol')); +/* +array ( + 0 => 'bar', + 1 => 'wut', +) +*/ + +var_export($client->keys('*')); +/* +array ( + 0 => 'nrk:foo', + 1 => 'nrk:lol', +) +*/ diff --git a/vendor/predis/predis/examples/lua_scripting_abstraction.php b/vendor/predis/predis/examples/lua_scripting_abstraction.php new file mode 100644 index 0000000..50c07bf --- /dev/null +++ b/vendor/predis/predis/examples/lua_scripting_abstraction.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// This example will not work with versions of Redis < 2.6. +// +// Additionally to the EVAL command defined in the current development profile, +// the Predis\Command\ScriptCommand class can be used to build an higher level +// abstraction for "scriptable" commands so that they will appear just like any +// other command on the client-side. This is a quick example used to implement +// INCREX. + +use Predis\Command\ScriptCommand; + +class IncrementExistingKeysBy extends ScriptCommand +{ + public function getKeysCount() + { + // Tell Predis to use all the arguments but the last one as arguments + // for KEYS. The last one will be used to populate ARGV. + return -1; + } + + public function getScript() + { + return << function ($options) { + $profile = $options->getDefault('profile'); + $profile->defineCommand('increxby', 'IncrementExistingKeysBy'); + + return $profile; + }, +)); + +$client->mset('foo', 10, 'foobar', 100); + +var_export($client->increxby('foo', 'foofoo', 'foobar', 50)); + +/* +array ( + 0 => 60, + 1 => NULL, + 2 => 150, +) +*/ diff --git a/vendor/predis/predis/examples/monitor_consumer.php b/vendor/predis/predis/examples/monitor_consumer.php new file mode 100644 index 0000000..d472049 --- /dev/null +++ b/vendor/predis/predis/examples/monitor_consumer.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// This is a basic example on how to use the Predis\Monitor\Consumer class. You +// can use redis-cli to send commands to the same Redis instance your client is +// connected to, and then type "ECHO QUIT_MONITOR" in redis-cli when you want to +// exit the monitor loop and terminate this script in a graceful way. + +// Create a client and disable r/w timeout on the socket. +$client = new Predis\Client($single_server + array('read_write_timeout' => 0)); + +// Use only one instance of DateTime, we will update the timestamp later. +$timestamp = new DateTime(); + +foreach (($monitor = $client->monitor()) as $event) { + $timestamp->setTimestamp((int) $event->timestamp); + + // If we notice a ECHO command with the message QUIT_MONITOR, we stop the + // monitor consumer and then break the loop. + if ($event->command === 'ECHO' && $event->arguments === '"QUIT_MONITOR"') { + echo 'Exiting the monitor loop...', PHP_EOL; + $monitor->stop(); + break; + } + + echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}", PHP_EOL; + if (isset($event->arguments)) { + echo " Arguments: {$event->arguments}", PHP_EOL; + } +} + +// Say goodbye :-) +$version = redis_version($client->info()); +echo "Goodbye from Redis $version!", PHP_EOL; diff --git a/vendor/predis/predis/examples/pipelining_commands.php b/vendor/predis/predis/examples/pipelining_commands.php new file mode 100644 index 0000000..632ef94 --- /dev/null +++ b/vendor/predis/predis/examples/pipelining_commands.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// When you have a whole set of consecutive commands to send to a redis server, +// you can use a pipeline to dramatically improve performances. Pipelines can +// greatly reduce the effects of network round-trips. + +$client = new Predis\Client($single_server); + +$responses = $client->pipeline(function ($pipe) { + $pipe->flushdb(); + $pipe->incrby('counter', 10); + $pipe->incrby('counter', 30); + $pipe->exists('counter'); + $pipe->get('counter'); + $pipe->mget('does_not_exist', 'counter'); +}); + +var_export($responses); + +/* OUTPUT: +array ( + 0 => Predis\Response\Status::__set_state(array( + 'payload' => 'OK', + )), + 1 => 10, + 2 => 40, + 3 => true, + 4 => '40', + 5 => array ( + 0 => NULL, + 1 => '40', + ), +) +*/ diff --git a/vendor/predis/predis/examples/pubsub_consumer.php b/vendor/predis/predis/examples/pubsub_consumer.php new file mode 100644 index 0000000..24c485c --- /dev/null +++ b/vendor/predis/predis/examples/pubsub_consumer.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// Starting from Redis 2.0 clients can subscribe and listen for events published +// on certain channels using a Publish/Subscribe (PUB/SUB) approach. + +// Create a client and disable r/w timeout on the socket +$client = new Predis\Client($single_server + array('read_write_timeout' => 0)); + +// Initialize a new pubsub consumer. +$pubsub = $client->pubSubLoop(); + +// Subscribe to your channels +$pubsub->subscribe('control_channel', 'notifications'); + +// Start processing the pubsup messages. Open a terminal and use redis-cli +// to push messages to the channels. Examples: +// ./redis-cli PUBLISH notifications "this is a test" +// ./redis-cli PUBLISH control_channel quit_loop +foreach ($pubsub as $message) { + switch ($message->kind) { + case 'subscribe': + echo "Subscribed to {$message->channel}", PHP_EOL; + break; + + case 'message': + if ($message->channel == 'control_channel') { + if ($message->payload == 'quit_loop') { + echo 'Aborting pubsub loop...', PHP_EOL; + $pubsub->unsubscribe(); + } else { + echo "Received an unrecognized command: {$message->payload}.", PHP_EOL; + } + } else { + echo "Received the following message from {$message->channel}:", + PHP_EOL, " {$message->payload}", PHP_EOL, PHP_EOL; + } + break; + } +} + +// Always unset the pubsub consumer instance when you are done! The +// class destructor will take care of cleanups and prevent protocol +// desynchronizations between the client and the server. +unset($pubsub); + +// Say goodbye :-) +$version = redis_version($client->info()); +echo "Goodbye from Redis $version!", PHP_EOL; diff --git a/vendor/predis/predis/examples/redis_collections_iterators.php b/vendor/predis/predis/examples/redis_collections_iterators.php new file mode 100644 index 0000000..62755c9 --- /dev/null +++ b/vendor/predis/predis/examples/redis_collections_iterators.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +use Predis\Collection\Iterator; + +// Starting from Redis 2.8, clients can iterate incrementally over collections +// without blocking the server like it happens when a command such as KEYS is +// executed on a Redis instance storing millions of keys. These commands are: +// +// - SCAN (iterates over the keyspace) +// - SSCAN (iterates over members of a set) +// - ZSCAN (iterates over members and ranks of a sorted set) +// - HSCAN (iterates over fields and values of an hash). + +// Predis provides a specialized abstraction for each command based on standard +// SPL iterators making it possible to easily consume SCAN-based iterations in +// your PHP code. +// +// See http://redis.io/commands/scan for more details. +// + +// Create a client using `2.8` as a server profile (needs Redis 2.8!) +$client = new Predis\Client($single_server, array('profile' => '2.8')); + +// Prepare some keys for our example +$client->del('predis:set', 'predis:zset', 'predis:hash'); +for ($i = 0; $i < 5; ++$i) { + $client->sadd('predis:set', "member:$i"); + $client->zadd('predis:zset', -$i, "member:$i"); + $client->hset('predis:hash', "field:$i", "value:$i"); +} + +// === Keyspace iterator based on SCAN === +echo 'Scan the keyspace matching only our prefixed keys:', PHP_EOL; +foreach (new Iterator\Keyspace($client, 'predis:*') as $key) { + echo " - $key", PHP_EOL; +} + +/* OUTPUT +Scan the keyspace matching only our prefixed keys: + - predis:zset + - predis:set + - predis:hash +*/ + +// === Set iterator based on SSCAN === +echo 'Scan members of `predis:set`:', PHP_EOL; +foreach (new Iterator\SetKey($client, 'predis:set') as $member) { + echo " - $member", PHP_EOL; +} + +/* OUTPUT +Scan members of `predis:set`: + - member:1 + - member:4 + - member:0 + - member:3 + - member:2 +*/ + +// === Sorted set iterator based on ZSCAN === +echo 'Scan members and ranks of `predis:zset`:', PHP_EOL; +foreach (new Iterator\SortedSetKey($client, 'predis:zset') as $member => $rank) { + echo " - $member [rank: $rank]", PHP_EOL; +} + +/* OUTPUT +Scan members and ranks of `predis:zset`: + - member:4 [rank: -4] + - member:3 [rank: -3] + - member:2 [rank: -2] + - member:1 [rank: -1] + - member:0 [rank: 0] +*/ + +// === Hash iterator based on HSCAN === +echo 'Scan fields and values of `predis:hash`:', PHP_EOL; +foreach (new Iterator\HashKey($client, 'predis:hash') as $field => $value) { + echo " - $field => $value", PHP_EOL; +} + +/* OUTPUT +Scan fields and values of `predis:hash`: + - field:0 => value:0 + - field:1 => value:1 + - field:2 => value:2 + - field:3 => value:3 + - field:4 => value:4 +*/ diff --git a/vendor/predis/predis/examples/replication_complex.php b/vendor/predis/predis/examples/replication_complex.php new file mode 100644 index 0000000..71193d2 --- /dev/null +++ b/vendor/predis/predis/examples/replication_complex.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// Predis allows to set Lua scripts as read-only operations for replication. +// This works for both EVAL and EVALSHA and also for the client-side abstraction +// built upon them (Predis\Command\ScriptCommand). This example shows a slightly +// more complex configuration that injects a new script command in the server +// profile used by the new client instance and marks it marks it as a read-only +// operation for replication so that it will be executed on slaves. + +use Predis\Command\ScriptCommand; +use Predis\Connection\Aggregate\MasterSlaveReplication; +use Predis\Replication\ReplicationStrategy; + +// ------------------------------------------------------------------------- // + +// Define a new script command that returns all the fields of a variable number +// of hashes with a single roundtrip. + +class HashMultipleGetAll extends ScriptCommand +{ + const BODY = << function ($options, $option) { + $profile = $options->getDefault($option); + $profile->defineCommand('hmgetall', 'HashMultipleGetAll'); + + return $profile; + }, + 'replication' => function () { + $strategy = new ReplicationStrategy(); + $strategy->setScriptReadOnly(HashMultipleGetAll::BODY); + + $replication = new MasterSlaveReplication($strategy); + + return $replication; + }, +); + +// ------------------------------------------------------------------------- // + +$client = new Predis\Client($parameters, $options); + +// Execute the following commands on the master server using redis-cli: +// $ ./redis-cli HMSET metavars foo bar hoge piyo +// $ ./redis-cli HMSET servers master host1 slave host2 + +$hashes = $client->hmgetall('metavars', 'servers'); + +$replication = $client->getConnection(); +$stillOnSlave = $replication->getCurrent() === $replication->getConnectionById('slave'); + +echo 'Is still on slave? ', $stillOnSlave ? 'YES!' : 'NO!', PHP_EOL; +var_export($hashes); diff --git a/vendor/predis/predis/examples/replication_sentinel.php b/vendor/predis/predis/examples/replication_sentinel.php new file mode 100644 index 0000000..f1e0de8 --- /dev/null +++ b/vendor/predis/predis/examples/replication_sentinel.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// Predis supports redis-sentinel to provide high availability in master / slave +// scenarios. The only but relevant difference with a basic replication scenario +// is that sentinel servers can manage the master server and its slaves based on +// their state, which means that they are able to provide an authoritative and +// updated configuration to clients thus avoiding static configurations for the +// replication servers and their roles. + +// Instead of connection parameters pointing to redis nodes, we provide a list +// of instances of redis-sentinel. Users should always provide a timeout value +// low enough to not hinder operations just in case a sentinel is unreachable +// but Predis uses a default value of 100 milliseconds for sentinel parameters +// without an explicit timeout value. +// +// NOTE: in real-world scenarios sentinels should be running on different hosts! +$sentinels = array( + 'tcp://127.0.0.1:5380?timeout=0.100', + 'tcp://127.0.0.1:5381?timeout=0.100', + 'tcp://127.0.0.1:5382?timeout=0.100', +); + +$client = new Predis\Client($sentinels, array( + 'replication' => 'sentinel', + 'service' => 'mymaster', +)); + +// Read operation. +$exists = $client->exists('foo') ? 'yes' : 'no'; +$current = $client->getConnection()->getCurrent()->getParameters(); +echo "Does 'foo' exist on {$current->alias}? $exists.", PHP_EOL; + +// Write operation. +$client->set('foo', 'bar'); +$current = $client->getConnection()->getCurrent()->getParameters(); +echo "Now 'foo' has been set to 'bar' on {$current->alias}!", PHP_EOL; + +// Read operation. +$bar = $client->get('foo'); +$current = $client->getConnection()->getCurrent()->getParameters(); +echo "We fetched 'foo' from {$current->alias} and its value is '$bar'.", PHP_EOL; + +/* OUTPUT: +Does 'foo' exist on slave-127.0.0.1:6381? yes. +Now 'foo' has been set to 'bar' on master! +We fetched 'foo' from master and its value is 'bar'. +*/ diff --git a/vendor/predis/predis/examples/replication_simple.php b/vendor/predis/predis/examples/replication_simple.php new file mode 100644 index 0000000..91b6db9 --- /dev/null +++ b/vendor/predis/predis/examples/replication_simple.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// Predis supports master / slave replication scenarios where write operations +// are performed on the master server and read operations are executed against +// one of the slaves. The behavior of commands or EVAL scripts can be customized +// at will. As soon as a write operation is performed the client switches to the +// master server for all the subsequent requests (either reads and writes). +// +// This example must be executed using the second Redis server configured as the +// slave of the first one (see the "SLAVEOF" command). +// + +$parameters = array( + 'tcp://127.0.0.1:6379?database=15&alias=master', + 'tcp://127.0.0.1:6380?database=15&alias=slave', +); + +$options = array('replication' => true); + +$client = new Predis\Client($parameters, $options); + +// Read operation. +$exists = $client->exists('foo') ? 'yes' : 'no'; +$current = $client->getConnection()->getCurrent()->getParameters(); +echo "Does 'foo' exist on {$current->alias}? $exists.", PHP_EOL; + +// Write operation. +$client->set('foo', 'bar'); +$current = $client->getConnection()->getCurrent()->getParameters(); +echo "Now 'foo' has been set to 'bar' on {$current->alias}!", PHP_EOL; + +// Read operation. +$bar = $client->get('foo'); +$current = $client->getConnection()->getCurrent()->getParameters(); +echo "We fetched 'foo' from {$current->alias} and its value is '$bar'.", PHP_EOL; + +/* OUTPUT: +Does 'foo' exist on slave? yes. +Now 'foo' has been set to 'bar' on master! +We fetched 'foo' from master and its value is 'bar'. +*/ diff --git a/vendor/predis/predis/examples/session_handler.php b/vendor/predis/predis/examples/session_handler.php new file mode 100644 index 0000000..4868a4d --- /dev/null +++ b/vendor/predis/predis/examples/session_handler.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// This example demonstrates how to use Predis to save PHP sessions on Redis. +// +// The value of `session.gc_maxlifetime` in `php.ini` will be used by default as +// the TTL for keys holding session data but this value can be overridden when +// creating the session handler instance using the `gc_maxlifetime` option. +// +// NOTE: this class requires PHP >= 5.4 but can be used on PHP 5.3 if a polyfill +// for SessionHandlerInterface is provided either by you or an external package +// like `symfony/http-foundation`. +// +// See http://www.php.net/class.sessionhandlerinterface.php for more details. +// + +if (!interface_exists('SessionHandlerInterface')) { + die('ATTENTION: the session handler implemented by Predis requires PHP >= 5.4.0 '. + "or a polyfill for SessionHandlerInterface provided by an external package.\n"); +} + +// Instantiate a new client just like you would normally do. Using a prefix for +// keys will effectively prefix all session keys with the specified string. +$client = new Predis\Client($single_server, array('prefix' => 'sessions:')); + +// Set `gc_maxlifetime` to specify a time-to-live of 5 seconds for session keys. +$handler = new Predis\Session\Handler($client, array('gc_maxlifetime' => 5)); + +// Register the session handler. +$handler->register(); + +// We just set a fixed session ID only for the sake of our example. +session_id('example_session_id'); + +session_start(); + +if (isset($_SESSION['foo'])) { + echo "Session has `foo` set to {$_SESSION['foo']}", PHP_EOL; +} else { + $_SESSION['foo'] = $value = mt_rand(); + echo "Empty session, `foo` has been set with $value", PHP_EOL; +} diff --git a/vendor/predis/predis/examples/shared.php b/vendor/predis/predis/examples/shared.php new file mode 100644 index 0000000..b65c9d3 --- /dev/null +++ b/vendor/predis/predis/examples/shared.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/../autoload.php'; + +function redis_version($info) +{ + if (isset($info['Server']['redis_version'])) { + return $info['Server']['redis_version']; + } elseif (isset($info['redis_version'])) { + return $info['redis_version']; + } else { + return 'unknown version'; + } +} + +$single_server = array( + 'host' => '127.0.0.1', + 'port' => 6379, + 'database' => 15, +); + +$multiple_servers = array( + array( + 'host' => '127.0.0.1', + 'port' => 6379, + 'database' => 15, + 'alias' => 'first', + ), + array( + 'host' => '127.0.0.1', + 'port' => 6380, + 'database' => 15, + 'alias' => 'second', + ), +); diff --git a/vendor/predis/predis/examples/transaction_using_cas.php b/vendor/predis/predis/examples/transaction_using_cas.php new file mode 100644 index 0000000..f72c465 --- /dev/null +++ b/vendor/predis/predis/examples/transaction_using_cas.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/shared.php'; + +// This is an implementation of an atomic client-side ZPOP using the support for +// check-and-set (CAS) operations with MULTI/EXEC transactions, as described in +// "WATCH explained" from http://redis.io/topics/transactions +// +// First, populate your database with a tiny sample data set: +// +// ./redis-cli +// SELECT 15 +// ZADD zset 1 a 2 b 3 c +// +// Then execute this script four times and see its output. +// + +function zpop($client, $key) +{ + $element = null; + $options = array( + 'cas' => true, // Initialize with support for CAS operations + 'watch' => $key, // Key that needs to be WATCHed to detect changes + 'retry' => 3, // Number of retries on aborted transactions, after + // which the client bails out with an exception. + ); + + $client->transaction($options, function ($tx) use ($key, &$element) { + @list($element) = $tx->zrange($key, 0, 0); + + if (isset($element)) { + $tx->multi(); // With CAS, MULTI *must* be explicitly invoked. + $tx->zrem($key, $element); + } + }); + + return $element; +} + +$client = new Predis\Client($single_server); +$zpopped = zpop($client, 'zset'); + +echo isset($zpopped) ? "ZPOPed $zpopped" : 'Nothing to ZPOP!', PHP_EOL; diff --git a/vendor/predis/predis/package.ini b/vendor/predis/predis/package.ini new file mode 100644 index 0000000..3984247 --- /dev/null +++ b/vendor/predis/predis/package.ini @@ -0,0 +1,36 @@ +; This file is meant to be used with Onion http://c9s.github.com/Onion/ +; For instructions on how to build a PEAR package of Predis please follow +; the instructions at this URL: +; +; https://github.com/c9s/Onion#a-quick-tutorial-for-building-pear-package +; + +[package] +name = "Predis" +desc = "Flexible and feature-complete Redis client for PHP and HHVM" +homepage = "http://github.com/nrk/predis" +license = "MIT" +version = "1.1.1" +stability = "stable" +channel = "pear.nrk.io" + +author = "Daniele Alessandri \"nrk\" " + +[require] +php = ">= 5.3.9" +pearinstaller = "1.4.1" + +[roles] +*.xml.dist = test +*.md = doc +LICENSE = doc + +[optional phpiredis] +hint = "Add support for faster protocol handling with phpiredis" +extensions[] = socket +extensions[] = phpiredis + +[optional webdis] +hint = "Add support for Webdis" +extensions[] = curl +extensions[] = phpiredis diff --git a/vendor/predis/predis/src/Autoloader.php b/vendor/predis/predis/src/Autoloader.php new file mode 100644 index 0000000..17ec2ff --- /dev/null +++ b/vendor/predis/predis/src/Autoloader.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 Predis; + +/** + * Implements a lightweight PSR-0 compliant autoloader for Predis. + * + * @author Eric Naeseth + * @author Daniele Alessandri + */ +class Autoloader +{ + private $directory; + private $prefix; + private $prefixLength; + + /** + * @param string $baseDirectory Base directory where the source files are located. + */ + public function __construct($baseDirectory = __DIR__) + { + $this->directory = $baseDirectory; + $this->prefix = __NAMESPACE__.'\\'; + $this->prefixLength = strlen($this->prefix); + } + + /** + * Registers the autoloader class with the PHP SPL autoloader. + * + * @param bool $prepend Prepend the autoloader on the stack instead of appending it. + */ + public static function register($prepend = false) + { + spl_autoload_register(array(new self(), 'autoload'), true, $prepend); + } + + /** + * Loads a class from a file using its fully qualified name. + * + * @param string $className Fully qualified name of a class. + */ + public function autoload($className) + { + if (0 === strpos($className, $this->prefix)) { + $parts = explode('\\', substr($className, $this->prefixLength)); + $filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php'; + + if (is_file($filepath)) { + require $filepath; + } + } + } +} diff --git a/vendor/predis/predis/src/Client.php b/vendor/predis/predis/src/Client.php new file mode 100644 index 0000000..efe5c8b --- /dev/null +++ b/vendor/predis/predis/src/Client.php @@ -0,0 +1,547 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis; + +use Predis\Command\CommandInterface; +use Predis\Command\RawCommand; +use Predis\Command\ScriptCommand; +use Predis\Configuration\Options; +use Predis\Configuration\OptionsInterface; +use Predis\Connection\AggregateConnectionInterface; +use Predis\Connection\ConnectionInterface; +use Predis\Connection\ParametersInterface; +use Predis\Monitor\Consumer as MonitorConsumer; +use Predis\Pipeline\Pipeline; +use Predis\PubSub\Consumer as PubSubConsumer; +use Predis\Response\ErrorInterface as ErrorResponseInterface; +use Predis\Response\ResponseInterface; +use Predis\Response\ServerException; +use Predis\Transaction\MultiExec as MultiExecTransaction; + +/** + * Client class used for connecting and executing commands on Redis. + * + * This is the main high-level abstraction of Predis upon which various other + * abstractions are built. Internally it aggregates various other classes each + * one with its own responsibility and scope. + * + * {@inheritdoc} + * + * @author Daniele Alessandri + */ +class Client implements ClientInterface, \IteratorAggregate +{ + const VERSION = '1.1.1'; + + protected $connection; + protected $options; + private $profile; + + /** + * @param mixed $parameters Connection parameters for one or more servers. + * @param mixed $options Options to configure some behaviours of the client. + */ + public function __construct($parameters = null, $options = null) + { + $this->options = $this->createOptions($options ?: array()); + $this->connection = $this->createConnection($parameters ?: array()); + $this->profile = $this->options->profile; + } + + /** + * Creates a new instance of Predis\Configuration\Options from different + * types of arguments or simply returns the passed argument if it is an + * instance of Predis\Configuration\OptionsInterface. + * + * @param mixed $options Client options. + * + * @throws \InvalidArgumentException + * + * @return OptionsInterface + */ + protected function createOptions($options) + { + if (is_array($options)) { + return new Options($options); + } + + if ($options instanceof OptionsInterface) { + return $options; + } + + throw new \InvalidArgumentException('Invalid type for client options.'); + } + + /** + * Creates single or aggregate connections from different types of arguments + * (string, array) or returns the passed argument if it is an instance of a + * class implementing Predis\Connection\ConnectionInterface. + * + * Accepted types for connection parameters are: + * + * - Instance of Predis\Connection\ConnectionInterface. + * - Instance of Predis\Connection\ParametersInterface. + * - Array + * - String + * - Callable + * + * @param mixed $parameters Connection parameters or connection instance. + * + * @throws \InvalidArgumentException + * + * @return ConnectionInterface + */ + protected function createConnection($parameters) + { + if ($parameters instanceof ConnectionInterface) { + return $parameters; + } + + if ($parameters instanceof ParametersInterface || is_string($parameters)) { + return $this->options->connections->create($parameters); + } + + if (is_array($parameters)) { + if (!isset($parameters[0])) { + return $this->options->connections->create($parameters); + } + + $options = $this->options; + + if ($options->defined('aggregate')) { + $initializer = $this->getConnectionInitializerWrapper($options->aggregate); + $connection = $initializer($parameters, $options); + } elseif ($options->defined('replication')) { + $replication = $options->replication; + + if ($replication instanceof AggregateConnectionInterface) { + $connection = $replication; + $options->connections->aggregate($connection, $parameters); + } else { + $initializer = $this->getConnectionInitializerWrapper($replication); + $connection = $initializer($parameters, $options); + } + } else { + $connection = $options->cluster; + $options->connections->aggregate($connection, $parameters); + } + + return $connection; + } + + if (is_callable($parameters)) { + $initializer = $this->getConnectionInitializerWrapper($parameters); + $connection = $initializer($this->options); + + return $connection; + } + + throw new \InvalidArgumentException('Invalid type for connection parameters.'); + } + + /** + * Wraps a callable to make sure that its returned value represents a valid + * connection type. + * + * @param mixed $callable + * + * @return \Closure + */ + protected function getConnectionInitializerWrapper($callable) + { + return function () use ($callable) { + $connection = call_user_func_array($callable, func_get_args()); + + if (!$connection instanceof ConnectionInterface) { + throw new \UnexpectedValueException( + 'The callable connection initializer returned an invalid type.' + ); + } + + return $connection; + }; + } + + /** + * {@inheritdoc} + */ + public function getProfile() + { + return $this->profile; + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->options; + } + + /** + * Creates a new client instance for the specified connection ID or alias, + * only when working with an aggregate connection (cluster, replication). + * The new client instances uses the same options of the original one. + * + * @param string $connectionID Identifier of a connection. + * + * @throws \InvalidArgumentException + * + * @return Client + */ + public function getClientFor($connectionID) + { + if (!$connection = $this->getConnectionById($connectionID)) { + throw new \InvalidArgumentException("Invalid connection ID: $connectionID."); + } + + return new static($connection, $this->options); + } + + /** + * Opens the underlying connection and connects to the server. + */ + public function connect() + { + $this->connection->connect(); + } + + /** + * Closes the underlying connection and disconnects from the server. + */ + public function disconnect() + { + $this->connection->disconnect(); + } + + /** + * Closes the underlying connection and disconnects from the server. + * + * This is the same as `Client::disconnect()` as it does not actually send + * the `QUIT` command to Redis, but simply closes the connection. + */ + public function quit() + { + $this->disconnect(); + } + + /** + * Returns the current state of the underlying connection. + * + * @return bool + */ + public function isConnected() + { + return $this->connection->isConnected(); + } + + /** + * {@inheritdoc} + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Retrieves the specified connection from the aggregate connection when the + * client is in cluster or replication mode. + * + * @param string $connectionID Index or alias of the single connection. + * + * @throws NotSupportedException + * + * @return Connection\NodeConnectionInterface + */ + public function getConnectionById($connectionID) + { + if (!$this->connection instanceof AggregateConnectionInterface) { + throw new NotSupportedException( + 'Retrieving connections by ID is supported only by aggregate connections.' + ); + } + + return $this->connection->getConnectionById($connectionID); + } + + /** + * Executes a command without filtering its arguments, parsing the response, + * applying any prefix to keys or throwing exceptions on Redis errors even + * regardless of client options. + * + * It is possible to identify Redis error responses from normal responses + * using the second optional argument which is populated by reference. + * + * @param array $arguments Command arguments as defined by the command signature. + * @param bool $error Set to TRUE when Redis returned an error response. + * + * @return mixed + */ + public function executeRaw(array $arguments, &$error = null) + { + $error = false; + + $response = $this->connection->executeCommand( + new RawCommand($arguments) + ); + + if ($response instanceof ResponseInterface) { + if ($response instanceof ErrorResponseInterface) { + $error = true; + } + + return (string) $response; + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function __call($commandID, $arguments) + { + return $this->executeCommand( + $this->createCommand($commandID, $arguments) + ); + } + + /** + * {@inheritdoc} + */ + public function createCommand($commandID, $arguments = array()) + { + return $this->profile->createCommand($commandID, $arguments); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + $response = $this->connection->executeCommand($command); + + if ($response instanceof ResponseInterface) { + if ($response instanceof ErrorResponseInterface) { + $response = $this->onErrorResponse($command, $response); + } + + return $response; + } + + return $command->parseResponse($response); + } + + /** + * Handles -ERR responses returned by Redis. + * + * @param CommandInterface $command Redis command that generated the error. + * @param ErrorResponseInterface $response Instance of the error response. + * + * @throws ServerException + * + * @return mixed + */ + protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response) + { + if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') { + $eval = $this->createCommand('EVAL'); + $eval->setRawArguments($command->getEvalArguments()); + + $response = $this->executeCommand($eval); + + if (!$response instanceof ResponseInterface) { + $response = $command->parseResponse($response); + } + + return $response; + } + + if ($this->options->exceptions) { + throw new ServerException($response->getMessage()); + } + + return $response; + } + + /** + * Executes the specified initializer method on `$this` by adjusting the + * actual invokation depending on the arity (0, 1 or 2 arguments). This is + * simply an utility method to create Redis contexts instances since they + * follow a common initialization path. + * + * @param string $initializer Method name. + * @param array $argv Arguments for the method. + * + * @return mixed + */ + private function sharedContextFactory($initializer, $argv = null) + { + switch (count($argv)) { + case 0: + return $this->$initializer(); + + case 1: + return is_array($argv[0]) + ? $this->$initializer($argv[0]) + : $this->$initializer(null, $argv[0]); + + case 2: + list($arg0, $arg1) = $argv; + + return $this->$initializer($arg0, $arg1); + + default: + return $this->$initializer($this, $argv); + } + } + + /** + * Creates a new pipeline context and returns it, or returns the results of + * a pipeline executed inside the optionally provided callable object. + * + * @param mixed ... Array of options, a callable for execution, or both. + * + * @return Pipeline|array + */ + public function pipeline(/* arguments */) + { + return $this->sharedContextFactory('createPipeline', func_get_args()); + } + + /** + * Actual pipeline context initializer method. + * + * @param array $options Options for the context. + * @param mixed $callable Optional callable used to execute the context. + * + * @return Pipeline|array + */ + protected function createPipeline(array $options = null, $callable = null) + { + if (isset($options['atomic']) && $options['atomic']) { + $class = 'Predis\Pipeline\Atomic'; + } elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) { + $class = 'Predis\Pipeline\FireAndForget'; + } else { + $class = 'Predis\Pipeline\Pipeline'; + } + + /* + * @var ClientContextInterface + */ + $pipeline = new $class($this); + + if (isset($callable)) { + return $pipeline->execute($callable); + } + + return $pipeline; + } + + /** + * Creates a new transaction context and returns it, or returns the results + * of a transaction executed inside the optionally provided callable object. + * + * @param mixed ... Array of options, a callable for execution, or both. + * + * @return MultiExecTransaction|array + */ + public function transaction(/* arguments */) + { + return $this->sharedContextFactory('createTransaction', func_get_args()); + } + + /** + * Actual transaction context initializer method. + * + * @param array $options Options for the context. + * @param mixed $callable Optional callable used to execute the context. + * + * @return MultiExecTransaction|array + */ + protected function createTransaction(array $options = null, $callable = null) + { + $transaction = new MultiExecTransaction($this, $options); + + if (isset($callable)) { + return $transaction->execute($callable); + } + + return $transaction; + } + + /** + * Creates a new publish/subscribe context and returns it, or starts its loop + * inside the optionally provided callable object. + * + * @param mixed ... Array of options, a callable for execution, or both. + * + * @return PubSubConsumer|null + */ + public function pubSubLoop(/* arguments */) + { + return $this->sharedContextFactory('createPubSub', func_get_args()); + } + + /** + * Actual publish/subscribe context initializer method. + * + * @param array $options Options for the context. + * @param mixed $callable Optional callable used to execute the context. + * + * @return PubSubConsumer|null + */ + protected function createPubSub(array $options = null, $callable = null) + { + $pubsub = new PubSubConsumer($this, $options); + + if (!isset($callable)) { + return $pubsub; + } + + foreach ($pubsub as $message) { + if (call_user_func($callable, $pubsub, $message) === false) { + $pubsub->stop(); + } + } + } + + /** + * Creates a new monitor consumer and returns it. + * + * @return MonitorConsumer + */ + public function monitor() + { + return new MonitorConsumer($this); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $clients = array(); + $connection = $this->getConnection(); + + if (!$connection instanceof \Traversable) { + throw new ClientException('The underlying connection is not traversable'); + } + + foreach ($connection as $node) { + $clients[(string) $node] = new static($node, $this->getOptions()); + } + + return new \ArrayIterator($clients); + } +} diff --git a/vendor/predis/predis/src/ClientContextInterface.php b/vendor/predis/predis/src/ClientContextInterface.php new file mode 100644 index 0000000..deb3865 --- /dev/null +++ b/vendor/predis/predis/src/ClientContextInterface.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis; + +use Predis\Command\CommandInterface; + +/** + * Interface defining a client-side context such as a pipeline or transaction. + * + * @method $this del(array $keys) + * @method $this dump($key) + * @method $this exists($key) + * @method $this expire($key, $seconds) + * @method $this expireat($key, $timestamp) + * @method $this keys($pattern) + * @method $this move($key, $db) + * @method $this object($subcommand, $key) + * @method $this persist($key) + * @method $this pexpire($key, $milliseconds) + * @method $this pexpireat($key, $timestamp) + * @method $this pttl($key) + * @method $this randomkey() + * @method $this rename($key, $target) + * @method $this renamenx($key, $target) + * @method $this scan($cursor, array $options = null) + * @method $this sort($key, array $options = null) + * @method $this ttl($key) + * @method $this type($key) + * @method $this append($key, $value) + * @method $this bitcount($key, $start = null, $end = null) + * @method $this bitop($operation, $destkey, $key) + * @method $this bitfield($key, $subcommand, ...$subcommandArg) + * @method $this decr($key) + * @method $this decrby($key, $decrement) + * @method $this get($key) + * @method $this getbit($key, $offset) + * @method $this getrange($key, $start, $end) + * @method $this getset($key, $value) + * @method $this incr($key) + * @method $this incrby($key, $increment) + * @method $this incrbyfloat($key, $increment) + * @method $this mget(array $keys) + * @method $this mset(array $dictionary) + * @method $this msetnx(array $dictionary) + * @method $this psetex($key, $milliseconds, $value) + * @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null) + * @method $this setbit($key, $offset, $value) + * @method $this setex($key, $seconds, $value) + * @method $this setnx($key, $value) + * @method $this setrange($key, $offset, $value) + * @method $this strlen($key) + * @method $this hdel($key, array $fields) + * @method $this hexists($key, $field) + * @method $this hget($key, $field) + * @method $this hgetall($key) + * @method $this hincrby($key, $field, $increment) + * @method $this hincrbyfloat($key, $field, $increment) + * @method $this hkeys($key) + * @method $this hlen($key) + * @method $this hmget($key, array $fields) + * @method $this hmset($key, array $dictionary) + * @method $this hscan($key, $cursor, array $options = null) + * @method $this hset($key, $field, $value) + * @method $this hsetnx($key, $field, $value) + * @method $this hvals($key) + * @method $this hstrlen($key, $field) + * @method $this blpop(array $keys, $timeout) + * @method $this brpop(array $keys, $timeout) + * @method $this brpoplpush($source, $destination, $timeout) + * @method $this lindex($key, $index) + * @method $this linsert($key, $whence, $pivot, $value) + * @method $this llen($key) + * @method $this lpop($key) + * @method $this lpush($key, array $values) + * @method $this lpushx($key, $value) + * @method $this lrange($key, $start, $stop) + * @method $this lrem($key, $count, $value) + * @method $this lset($key, $index, $value) + * @method $this ltrim($key, $start, $stop) + * @method $this rpop($key) + * @method $this rpoplpush($source, $destination) + * @method $this rpush($key, array $values) + * @method $this rpushx($key, $value) + * @method $this sadd($key, array $members) + * @method $this scard($key) + * @method $this sdiff(array $keys) + * @method $this sdiffstore($destination, array $keys) + * @method $this sinter(array $keys) + * @method $this sinterstore($destination, array $keys) + * @method $this sismember($key, $member) + * @method $this smembers($key) + * @method $this smove($source, $destination, $member) + * @method $this spop($key, $count = null) + * @method $this srandmember($key, $count = null) + * @method $this srem($key, $member) + * @method $this sscan($key, $cursor, array $options = null) + * @method $this sunion(array $keys) + * @method $this sunionstore($destination, array $keys) + * @method $this zadd($key, array $membersAndScoresDictionary) + * @method $this zcard($key) + * @method $this zcount($key, $min, $max) + * @method $this zincrby($key, $increment, $member) + * @method $this zinterstore($destination, array $keys, array $options = null) + * @method $this zrange($key, $start, $stop, array $options = null) + * @method $this zrangebyscore($key, $min, $max, array $options = null) + * @method $this zrank($key, $member) + * @method $this zrem($key, $member) + * @method $this zremrangebyrank($key, $start, $stop) + * @method $this zremrangebyscore($key, $min, $max) + * @method $this zrevrange($key, $start, $stop, array $options = null) + * @method $this zrevrangebyscore($key, $min, $max, array $options = null) + * @method $this zrevrank($key, $member) + * @method $this zunionstore($destination, array $keys, array $options = null) + * @method $this zscore($key, $member) + * @method $this zscan($key, $cursor, array $options = null) + * @method $this zrangebylex($key, $start, $stop, array $options = null) + * @method $this zrevrangebylex($key, $start, $stop, array $options = null) + * @method $this zremrangebylex($key, $min, $max) + * @method $this zlexcount($key, $min, $max) + * @method $this pfadd($key, array $elements) + * @method $this pfmerge($destinationKey, array $sourceKeys) + * @method $this pfcount(array $keys) + * @method $this pubsub($subcommand, $argument) + * @method $this publish($channel, $message) + * @method $this discard() + * @method $this exec() + * @method $this multi() + * @method $this unwatch() + * @method $this watch($key) + * @method $this eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null) + * @method $this evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null) + * @method $this script($subcommand, $argument = null) + * @method $this auth($password) + * @method $this echo($message) + * @method $this ping($message = null) + * @method $this select($database) + * @method $this bgrewriteaof() + * @method $this bgsave() + * @method $this client($subcommand, $argument = null) + * @method $this config($subcommand, $argument = null) + * @method $this dbsize() + * @method $this flushall() + * @method $this flushdb() + * @method $this info($section = null) + * @method $this lastsave() + * @method $this save() + * @method $this slaveof($host, $port) + * @method $this slowlog($subcommand, $argument = null) + * @method $this time() + * @method $this command() + * @method $this geoadd($key, $longitude, $latitude, $member) + * @method $this geohash($key, array $members) + * @method $this geopos($key, array $members) + * @method $this geodist($key, $member1, $member2, $unit = null) + * @method $this georadius($key, $longitude, $latitude, $radius, $unit, array $options = null) + * @method $this georadiusbymember($key, $member, $radius, $unit, array $options = null) + * + * @author Daniele Alessandri + */ +interface ClientContextInterface +{ + /** + * Sends the specified command instance to Redis. + * + * @param CommandInterface $command Command instance. + * + * @return mixed + */ + public function executeCommand(CommandInterface $command); + + /** + * Sends the specified command with its arguments to Redis. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + * + * @return mixed + */ + public function __call($method, $arguments); + + /** + * Starts the execution of the context. + * + * @param mixed $callable Optional callback for execution. + * + * @return array + */ + public function execute($callable = null); +} diff --git a/vendor/predis/predis/src/ClientException.php b/vendor/predis/predis/src/ClientException.php new file mode 100644 index 0000000..6c07aaf --- /dev/null +++ b/vendor/predis/predis/src/ClientException.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 Predis; + +/** + * Exception class that identifies client-side errors. + * + * @author Daniele Alessandri + */ +class ClientException extends PredisException +{ +} diff --git a/vendor/predis/predis/src/ClientInterface.php b/vendor/predis/predis/src/ClientInterface.php new file mode 100644 index 0000000..d062526 --- /dev/null +++ b/vendor/predis/predis/src/ClientInterface.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 Predis; + +use Predis\Command\CommandInterface; +use Predis\Configuration\OptionsInterface; +use Predis\Connection\ConnectionInterface; +use Predis\Profile\ProfileInterface; + +/** + * Interface defining a client able to execute commands against Redis. + * + * All the commands exposed by the client generally have the same signature as + * described by the Redis documentation, but some of them offer an additional + * and more friendly interface to ease programming which is described in the + * following list of methods: + * + * @method int del(array $keys) + * @method string dump($key) + * @method int exists($key) + * @method int expire($key, $seconds) + * @method int expireat($key, $timestamp) + * @method array keys($pattern) + * @method int move($key, $db) + * @method mixed object($subcommand, $key) + * @method int persist($key) + * @method int pexpire($key, $milliseconds) + * @method int pexpireat($key, $timestamp) + * @method int pttl($key) + * @method string randomkey() + * @method mixed rename($key, $target) + * @method int renamenx($key, $target) + * @method array scan($cursor, array $options = null) + * @method array sort($key, array $options = null) + * @method int ttl($key) + * @method mixed type($key) + * @method int append($key, $value) + * @method int bitcount($key, $start = null, $end = null) + * @method int bitop($operation, $destkey, $key) + * @method array bitfield($key, $subcommand, ...$subcommandArg) + * @method int decr($key) + * @method int decrby($key, $decrement) + * @method string get($key) + * @method int getbit($key, $offset) + * @method string getrange($key, $start, $end) + * @method string getset($key, $value) + * @method int incr($key) + * @method int incrby($key, $increment) + * @method string incrbyfloat($key, $increment) + * @method array mget(array $keys) + * @method mixed mset(array $dictionary) + * @method int msetnx(array $dictionary) + * @method mixed psetex($key, $milliseconds, $value) + * @method mixed set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null) + * @method int setbit($key, $offset, $value) + * @method int setex($key, $seconds, $value) + * @method int setnx($key, $value) + * @method int setrange($key, $offset, $value) + * @method int strlen($key) + * @method int hdel($key, array $fields) + * @method int hexists($key, $field) + * @method string hget($key, $field) + * @method array hgetall($key) + * @method int hincrby($key, $field, $increment) + * @method string hincrbyfloat($key, $field, $increment) + * @method array hkeys($key) + * @method int hlen($key) + * @method array hmget($key, array $fields) + * @method mixed hmset($key, array $dictionary) + * @method array hscan($key, $cursor, array $options = null) + * @method int hset($key, $field, $value) + * @method int hsetnx($key, $field, $value) + * @method array hvals($key) + * @method int hstrlen($key, $field) + * @method array blpop(array $keys, $timeout) + * @method array brpop(array $keys, $timeout) + * @method array brpoplpush($source, $destination, $timeout) + * @method string lindex($key, $index) + * @method int linsert($key, $whence, $pivot, $value) + * @method int llen($key) + * @method string lpop($key) + * @method int lpush($key, array $values) + * @method int lpushx($key, $value) + * @method array lrange($key, $start, $stop) + * @method int lrem($key, $count, $value) + * @method mixed lset($key, $index, $value) + * @method mixed ltrim($key, $start, $stop) + * @method string rpop($key) + * @method string rpoplpush($source, $destination) + * @method int rpush($key, array $values) + * @method int rpushx($key, $value) + * @method int sadd($key, array $members) + * @method int scard($key) + * @method array sdiff(array $keys) + * @method int sdiffstore($destination, array $keys) + * @method array sinter(array $keys) + * @method int sinterstore($destination, array $keys) + * @method int sismember($key, $member) + * @method array smembers($key) + * @method int smove($source, $destination, $member) + * @method string spop($key, $count = null) + * @method string srandmember($key, $count = null) + * @method int srem($key, $member) + * @method array sscan($key, $cursor, array $options = null) + * @method array sunion(array $keys) + * @method int sunionstore($destination, array $keys) + * @method int zadd($key, array $membersAndScoresDictionary) + * @method int zcard($key) + * @method string zcount($key, $min, $max) + * @method string zincrby($key, $increment, $member) + * @method int zinterstore($destination, array $keys, array $options = null) + * @method array zrange($key, $start, $stop, array $options = null) + * @method array zrangebyscore($key, $min, $max, array $options = null) + * @method int zrank($key, $member) + * @method int zrem($key, $member) + * @method int zremrangebyrank($key, $start, $stop) + * @method int zremrangebyscore($key, $min, $max) + * @method array zrevrange($key, $start, $stop, array $options = null) + * @method array zrevrangebyscore($key, $max, $min, array $options = null) + * @method int zrevrank($key, $member) + * @method int zunionstore($destination, array $keys, array $options = null) + * @method string zscore($key, $member) + * @method array zscan($key, $cursor, array $options = null) + * @method array zrangebylex($key, $start, $stop, array $options = null) + * @method array zrevrangebylex($key, $start, $stop, array $options = null) + * @method int zremrangebylex($key, $min, $max) + * @method int zlexcount($key, $min, $max) + * @method int pfadd($key, array $elements) + * @method mixed pfmerge($destinationKey, array $sourceKeys) + * @method int pfcount(array $keys) + * @method mixed pubsub($subcommand, $argument) + * @method int publish($channel, $message) + * @method mixed discard() + * @method array exec() + * @method mixed multi() + * @method mixed unwatch() + * @method mixed watch($key) + * @method mixed eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null) + * @method mixed evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null) + * @method mixed script($subcommand, $argument = null) + * @method mixed auth($password) + * @method string echo($message) + * @method mixed ping($message = null) + * @method mixed select($database) + * @method mixed bgrewriteaof() + * @method mixed bgsave() + * @method mixed client($subcommand, $argument = null) + * @method mixed config($subcommand, $argument = null) + * @method int dbsize() + * @method mixed flushall() + * @method mixed flushdb() + * @method array info($section = null) + * @method int lastsave() + * @method mixed save() + * @method mixed slaveof($host, $port) + * @method mixed slowlog($subcommand, $argument = null) + * @method array time() + * @method array command() + * @method int geoadd($key, $longitude, $latitude, $member) + * @method array geohash($key, array $members) + * @method array geopos($key, array $members) + * @method string geodist($key, $member1, $member2, $unit = null) + * @method array georadius($key, $longitude, $latitude, $radius, $unit, array $options = null) + * @method array georadiusbymember($key, $member, $radius, $unit, array $options = null) + * + * @author Daniele Alessandri + */ +interface ClientInterface +{ + /** + * Returns the server profile used by the client. + * + * @return ProfileInterface + */ + public function getProfile(); + + /** + * Returns the client options specified upon initialization. + * + * @return OptionsInterface + */ + public function getOptions(); + + /** + * Opens the underlying connection to the server. + */ + public function connect(); + + /** + * Closes the underlying connection from the server. + */ + public function disconnect(); + + /** + * Returns the underlying connection instance. + * + * @return ConnectionInterface + */ + public function getConnection(); + + /** + * Creates a new instance of the specified Redis command. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + * + * @return CommandInterface + */ + public function createCommand($method, $arguments = array()); + + /** + * Executes the specified Redis command. + * + * @param CommandInterface $command Command instance. + * + * @return mixed + */ + public function executeCommand(CommandInterface $command); + + /** + * Creates a Redis command with the specified arguments and sends a request + * to the server. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + * + * @return mixed + */ + public function __call($method, $arguments); +} diff --git a/vendor/predis/predis/src/Cluster/ClusterStrategy.php b/vendor/predis/predis/src/Cluster/ClusterStrategy.php new file mode 100644 index 0000000..1891907 --- /dev/null +++ b/vendor/predis/predis/src/Cluster/ClusterStrategy.php @@ -0,0 +1,469 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster; + +use Predis\Command\CommandInterface; +use Predis\Command\ScriptCommand; + +/** + * Common class implementing the logic needed to support clustering strategies. + * + * @author Daniele Alessandri + */ +abstract class ClusterStrategy implements StrategyInterface +{ + protected $commands; + + /** + * + */ + public function __construct() + { + $this->commands = $this->getDefaultCommands(); + } + + /** + * Returns the default map of supported commands with their handlers. + * + * @return array + */ + protected function getDefaultCommands() + { + $getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument'); + $getKeyFromAllArguments = array($this, 'getKeyFromAllArguments'); + + return array( + /* commands operating on the key space */ + 'EXISTS' => $getKeyFromAllArguments, + 'DEL' => $getKeyFromAllArguments, + 'TYPE' => $getKeyFromFirstArgument, + 'EXPIRE' => $getKeyFromFirstArgument, + 'EXPIREAT' => $getKeyFromFirstArgument, + 'PERSIST' => $getKeyFromFirstArgument, + 'PEXPIRE' => $getKeyFromFirstArgument, + 'PEXPIREAT' => $getKeyFromFirstArgument, + 'TTL' => $getKeyFromFirstArgument, + 'PTTL' => $getKeyFromFirstArgument, + 'SORT' => array($this, 'getKeyFromSortCommand'), + 'DUMP' => $getKeyFromFirstArgument, + 'RESTORE' => $getKeyFromFirstArgument, + + /* commands operating on string values */ + 'APPEND' => $getKeyFromFirstArgument, + 'DECR' => $getKeyFromFirstArgument, + 'DECRBY' => $getKeyFromFirstArgument, + 'GET' => $getKeyFromFirstArgument, + 'GETBIT' => $getKeyFromFirstArgument, + 'MGET' => $getKeyFromAllArguments, + 'SET' => $getKeyFromFirstArgument, + 'GETRANGE' => $getKeyFromFirstArgument, + 'GETSET' => $getKeyFromFirstArgument, + 'INCR' => $getKeyFromFirstArgument, + 'INCRBY' => $getKeyFromFirstArgument, + 'INCRBYFLOAT' => $getKeyFromFirstArgument, + 'SETBIT' => $getKeyFromFirstArgument, + 'SETEX' => $getKeyFromFirstArgument, + 'MSET' => array($this, 'getKeyFromInterleavedArguments'), + 'MSETNX' => array($this, 'getKeyFromInterleavedArguments'), + 'SETNX' => $getKeyFromFirstArgument, + 'SETRANGE' => $getKeyFromFirstArgument, + 'STRLEN' => $getKeyFromFirstArgument, + 'SUBSTR' => $getKeyFromFirstArgument, + 'BITOP' => array($this, 'getKeyFromBitOp'), + 'BITCOUNT' => $getKeyFromFirstArgument, + 'BITFIELD' => $getKeyFromFirstArgument, + + /* commands operating on lists */ + 'LINSERT' => $getKeyFromFirstArgument, + 'LINDEX' => $getKeyFromFirstArgument, + 'LLEN' => $getKeyFromFirstArgument, + 'LPOP' => $getKeyFromFirstArgument, + 'RPOP' => $getKeyFromFirstArgument, + 'RPOPLPUSH' => $getKeyFromAllArguments, + 'BLPOP' => array($this, 'getKeyFromBlockingListCommands'), + 'BRPOP' => array($this, 'getKeyFromBlockingListCommands'), + 'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'), + 'LPUSH' => $getKeyFromFirstArgument, + 'LPUSHX' => $getKeyFromFirstArgument, + 'RPUSH' => $getKeyFromFirstArgument, + 'RPUSHX' => $getKeyFromFirstArgument, + 'LRANGE' => $getKeyFromFirstArgument, + 'LREM' => $getKeyFromFirstArgument, + 'LSET' => $getKeyFromFirstArgument, + 'LTRIM' => $getKeyFromFirstArgument, + + /* commands operating on sets */ + 'SADD' => $getKeyFromFirstArgument, + 'SCARD' => $getKeyFromFirstArgument, + 'SDIFF' => $getKeyFromAllArguments, + 'SDIFFSTORE' => $getKeyFromAllArguments, + 'SINTER' => $getKeyFromAllArguments, + 'SINTERSTORE' => $getKeyFromAllArguments, + 'SUNION' => $getKeyFromAllArguments, + 'SUNIONSTORE' => $getKeyFromAllArguments, + 'SISMEMBER' => $getKeyFromFirstArgument, + 'SMEMBERS' => $getKeyFromFirstArgument, + 'SSCAN' => $getKeyFromFirstArgument, + 'SPOP' => $getKeyFromFirstArgument, + 'SRANDMEMBER' => $getKeyFromFirstArgument, + 'SREM' => $getKeyFromFirstArgument, + + /* commands operating on sorted sets */ + 'ZADD' => $getKeyFromFirstArgument, + 'ZCARD' => $getKeyFromFirstArgument, + 'ZCOUNT' => $getKeyFromFirstArgument, + 'ZINCRBY' => $getKeyFromFirstArgument, + 'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'), + 'ZRANGE' => $getKeyFromFirstArgument, + 'ZRANGEBYSCORE' => $getKeyFromFirstArgument, + 'ZRANK' => $getKeyFromFirstArgument, + 'ZREM' => $getKeyFromFirstArgument, + 'ZREMRANGEBYRANK' => $getKeyFromFirstArgument, + 'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument, + 'ZREVRANGE' => $getKeyFromFirstArgument, + 'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument, + 'ZREVRANK' => $getKeyFromFirstArgument, + 'ZSCORE' => $getKeyFromFirstArgument, + 'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'), + 'ZSCAN' => $getKeyFromFirstArgument, + 'ZLEXCOUNT' => $getKeyFromFirstArgument, + 'ZRANGEBYLEX' => $getKeyFromFirstArgument, + 'ZREMRANGEBYLEX' => $getKeyFromFirstArgument, + 'ZREVRANGEBYLEX' => $getKeyFromFirstArgument, + + /* commands operating on hashes */ + 'HDEL' => $getKeyFromFirstArgument, + 'HEXISTS' => $getKeyFromFirstArgument, + 'HGET' => $getKeyFromFirstArgument, + 'HGETALL' => $getKeyFromFirstArgument, + 'HMGET' => $getKeyFromFirstArgument, + 'HMSET' => $getKeyFromFirstArgument, + 'HINCRBY' => $getKeyFromFirstArgument, + 'HINCRBYFLOAT' => $getKeyFromFirstArgument, + 'HKEYS' => $getKeyFromFirstArgument, + 'HLEN' => $getKeyFromFirstArgument, + 'HSET' => $getKeyFromFirstArgument, + 'HSETNX' => $getKeyFromFirstArgument, + 'HVALS' => $getKeyFromFirstArgument, + 'HSCAN' => $getKeyFromFirstArgument, + 'HSTRLEN' => $getKeyFromFirstArgument, + + /* commands operating on HyperLogLog */ + 'PFADD' => $getKeyFromFirstArgument, + 'PFCOUNT' => $getKeyFromAllArguments, + 'PFMERGE' => $getKeyFromAllArguments, + + /* scripting */ + 'EVAL' => array($this, 'getKeyFromScriptingCommands'), + 'EVALSHA' => array($this, 'getKeyFromScriptingCommands'), + + /* commands performing geospatial operations */ + 'GEOADD' => $getKeyFromFirstArgument, + 'GEOHASH' => $getKeyFromFirstArgument, + 'GEOPOS' => $getKeyFromFirstArgument, + 'GEODIST' => $getKeyFromFirstArgument, + 'GEORADIUS' => array($this, 'getKeyFromGeoradiusCommands'), + 'GEORADIUSBYMEMBER' => array($this, 'getKeyFromGeoradiusCommands'), + ); + } + + /** + * Returns the list of IDs for the supported commands. + * + * @return array + */ + public function getSupportedCommands() + { + return array_keys($this->commands); + } + + /** + * Sets an handler for the specified command ID. + * + * The signature of the callback must have a single parameter of type + * Predis\Command\CommandInterface. + * + * When the callback argument is omitted or NULL, the previously associated + * handler for the specified command ID is removed. + * + * @param string $commandID Command ID. + * @param mixed $callback A valid callable object, or NULL to unset the handler. + * + * @throws \InvalidArgumentException + */ + public function setCommandHandler($commandID, $callback = null) + { + $commandID = strtoupper($commandID); + + if (!isset($callback)) { + unset($this->commands[$commandID]); + + return; + } + + if (!is_callable($callback)) { + throw new \InvalidArgumentException( + 'The argument must be a callable object or NULL.' + ); + } + + $this->commands[$commandID] = $callback; + } + + /** + * Extracts the key from the first argument of a command instance. + * + * @param CommandInterface $command Command instance. + * + * @return string + */ + protected function getKeyFromFirstArgument(CommandInterface $command) + { + return $command->getArgument(0); + } + + /** + * Extracts the key from a command with multiple keys only when all keys in + * the arguments array produce the same hash. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromAllArguments(CommandInterface $command) + { + $arguments = $command->getArguments(); + + if ($this->checkSameSlotForKeys($arguments)) { + return $arguments[0]; + } + } + + /** + * Extracts the key from a command with multiple keys only when all keys in + * the arguments array produce the same hash. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromInterleavedArguments(CommandInterface $command) + { + $arguments = $command->getArguments(); + $keys = array(); + + for ($i = 0; $i < count($arguments); $i += 2) { + $keys[] = $arguments[$i]; + } + + if ($this->checkSameSlotForKeys($keys)) { + return $arguments[0]; + } + } + + /** + * Extracts the key from SORT command. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromSortCommand(CommandInterface $command) + { + $arguments = $command->getArguments(); + $firstKey = $arguments[0]; + + if (1 === $argc = count($arguments)) { + return $firstKey; + } + + $keys = array($firstKey); + + for ($i = 1; $i < $argc; ++$i) { + if (strtoupper($arguments[$i]) === 'STORE') { + $keys[] = $arguments[++$i]; + } + } + + if ($this->checkSameSlotForKeys($keys)) { + return $firstKey; + } + } + + /** + * Extracts the key from BLPOP and BRPOP commands. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromBlockingListCommands(CommandInterface $command) + { + $arguments = $command->getArguments(); + + if ($this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) { + return $arguments[0]; + } + } + + /** + * Extracts the key from BITOP command. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromBitOp(CommandInterface $command) + { + $arguments = $command->getArguments(); + + if ($this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) { + return $arguments[1]; + } + } + + /** + * Extracts the key from GEORADIUS and GEORADIUSBYMEMBER commands. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromGeoradiusCommands(CommandInterface $command) + { + $arguments = $command->getArguments(); + $argc = count($arguments); + $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; + + if ($argc > $startIndex) { + $keys = array($arguments[0]); + + for ($i = $startIndex; $i < $argc; ++$i) { + $argument = strtoupper($arguments[$i]); + if ($argument === 'STORE' || $argument === 'STOREDIST') { + $keys[] = $arguments[++$i]; + } + } + + if ($this->checkSameSlotForKeys($keys)) { + return $arguments[0]; + } else { + return; + } + } + + return $arguments[0]; + } + + /** + * Extracts the key from ZINTERSTORE and ZUNIONSTORE commands. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromZsetAggregationCommands(CommandInterface $command) + { + $arguments = $command->getArguments(); + $keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1])); + + if ($this->checkSameSlotForKeys($keys)) { + return $arguments[0]; + } + } + + /** + * Extracts the key from EVAL and EVALSHA commands. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromScriptingCommands(CommandInterface $command) + { + if ($command instanceof ScriptCommand) { + $keys = $command->getKeys(); + } else { + $keys = array_slice($args = $command->getArguments(), 2, $args[1]); + } + + if ($keys && $this->checkSameSlotForKeys($keys)) { + return $keys[0]; + } + } + + /** + * {@inheritdoc} + */ + public function getSlot(CommandInterface $command) + { + $slot = $command->getSlot(); + + if (!isset($slot) && isset($this->commands[$cmdID = $command->getId()])) { + $key = call_user_func($this->commands[$cmdID], $command); + + if (isset($key)) { + $slot = $this->getSlotByKey($key); + $command->setSlot($slot); + } + } + + return $slot; + } + + /** + * Checks if the specified array of keys will generate the same hash. + * + * @param array $keys Array of keys. + * + * @return bool + */ + protected function checkSameSlotForKeys(array $keys) + { + if (!$count = count($keys)) { + return false; + } + + $currentSlot = $this->getSlotByKey($keys[0]); + + for ($i = 1; $i < $count; ++$i) { + $nextSlot = $this->getSlotByKey($keys[$i]); + + if ($currentSlot !== $nextSlot) { + return false; + } + + $currentSlot = $nextSlot; + } + + return true; + } + + /** + * Returns only the hashable part of a key (delimited by "{...}"), or the + * whole key if a key tag is not found in the string. + * + * @param string $key A key. + * + * @return string + */ + protected function extractKeyTag($key) + { + if (false !== $start = strpos($key, '{')) { + if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) { + $key = substr($key, $start, $end - $start); + } + } + + return $key; + } +} diff --git a/vendor/predis/predis/src/Cluster/Distributor/DistributorInterface.php b/vendor/predis/predis/src/Cluster/Distributor/DistributorInterface.php new file mode 100644 index 0000000..831f52c --- /dev/null +++ b/vendor/predis/predis/src/Cluster/Distributor/DistributorInterface.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 Predis\Cluster\Distributor; + +use Predis\Cluster\Hash\HashGeneratorInterface; + +/** + * A distributor implements the logic to automatically distribute keys among + * several nodes for client-side sharding. + * + * @author Daniele Alessandri + */ +interface DistributorInterface +{ + /** + * Adds a node to the distributor with an optional weight. + * + * @param mixed $node Node object. + * @param int $weight Weight for the node. + */ + public function add($node, $weight = null); + + /** + * Removes a node from the distributor. + * + * @param mixed $node Node object. + */ + public function remove($node); + + /** + * Returns the corresponding slot of a node from the distributor using the + * computed hash of a key. + * + * @param mixed $hash + * + * @return mixed + */ + public function getSlot($hash); + + /** + * Returns a node from the distributor using its assigned slot ID. + * + * @param mixed $slot + * + * @return mixed|null + */ + public function getBySlot($slot); + + /** + * Returns a node from the distributor using the computed hash of a key. + * + * @param mixed $hash + * + * @return mixed + */ + public function getByHash($hash); + + /** + * Returns a node from the distributor mapping to the specified value. + * + * @param string $value + * + * @return mixed + */ + public function get($value); + + /** + * Returns the underlying hash generator instance. + * + * @return HashGeneratorInterface + */ + public function getHashGenerator(); +} diff --git a/vendor/predis/predis/src/Cluster/Distributor/EmptyRingException.php b/vendor/predis/predis/src/Cluster/Distributor/EmptyRingException.php new file mode 100644 index 0000000..039f2f2 --- /dev/null +++ b/vendor/predis/predis/src/Cluster/Distributor/EmptyRingException.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 Predis\Cluster\Distributor; + +/** + * Exception class that identifies empty rings. + * + * @author Daniele Alessandri + */ +class EmptyRingException extends \Exception +{ +} diff --git a/vendor/predis/predis/src/Cluster/Distributor/HashRing.php b/vendor/predis/predis/src/Cluster/Distributor/HashRing.php new file mode 100644 index 0000000..db864d9 --- /dev/null +++ b/vendor/predis/predis/src/Cluster/Distributor/HashRing.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster\Distributor; + +use Predis\Cluster\Hash\HashGeneratorInterface; + +/** + * This class implements an hashring-based distributor that uses the same + * algorithm of memcache to distribute keys in a cluster using client-side + * sharding. + * + * @author Daniele Alessandri + * @author Lorenzo Castelli + */ +class HashRing implements DistributorInterface, HashGeneratorInterface +{ + const DEFAULT_REPLICAS = 128; + const DEFAULT_WEIGHT = 100; + + private $ring; + private $ringKeys; + private $ringKeysCount; + private $replicas; + private $nodeHashCallback; + private $nodes = array(); + + /** + * @param int $replicas Number of replicas in the ring. + * @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes. + */ + public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null) + { + $this->replicas = $replicas; + $this->nodeHashCallback = $nodeHashCallback; + } + + /** + * Adds a node to the ring with an optional weight. + * + * @param mixed $node Node object. + * @param int $weight Weight for the node. + */ + public function add($node, $weight = null) + { + // In case of collisions in the hashes of the nodes, the node added + // last wins, thus the order in which nodes are added is significant. + $this->nodes[] = array( + 'object' => $node, + 'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT, + ); + + $this->reset(); + } + + /** + * {@inheritdoc} + */ + public function remove($node) + { + // A node is removed by resetting the ring so that it's recreated from + // scratch, in order to reassign possible hashes with collisions to the + // right node according to the order in which they were added in the + // first place. + for ($i = 0; $i < count($this->nodes); ++$i) { + if ($this->nodes[$i]['object'] === $node) { + array_splice($this->nodes, $i, 1); + $this->reset(); + + break; + } + } + } + + /** + * Resets the distributor. + */ + private function reset() + { + unset( + $this->ring, + $this->ringKeys, + $this->ringKeysCount + ); + } + + /** + * Returns the initialization status of the distributor. + * + * @return bool + */ + private function isInitialized() + { + return isset($this->ringKeys); + } + + /** + * Calculates the total weight of all the nodes in the distributor. + * + * @return int + */ + private function computeTotalWeight() + { + $totalWeight = 0; + + foreach ($this->nodes as $node) { + $totalWeight += $node['weight']; + } + + return $totalWeight; + } + + /** + * Initializes the distributor. + */ + private function initialize() + { + if ($this->isInitialized()) { + return; + } + + if (!$this->nodes) { + throw new EmptyRingException('Cannot initialize an empty hashring.'); + } + + $this->ring = array(); + $totalWeight = $this->computeTotalWeight(); + $nodesCount = count($this->nodes); + + foreach ($this->nodes as $node) { + $weightRatio = $node['weight'] / $totalWeight; + $this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio); + } + + ksort($this->ring, SORT_NUMERIC); + $this->ringKeys = array_keys($this->ring); + $this->ringKeysCount = count($this->ringKeys); + } + + /** + * Implements the logic needed to add a node to the hashring. + * + * @param array $ring Source hashring. + * @param mixed $node Node object to be added. + * @param int $totalNodes Total number of nodes. + * @param int $replicas Number of replicas in the ring. + * @param float $weightRatio Weight ratio for the node. + */ + protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio) + { + $nodeObject = $node['object']; + $nodeHash = $this->getNodeHash($nodeObject); + $replicas = (int) round($weightRatio * $totalNodes * $replicas); + + for ($i = 0; $i < $replicas; ++$i) { + $key = crc32("$nodeHash:$i"); + $ring[$key] = $nodeObject; + } + } + + /** + * {@inheritdoc} + */ + protected function getNodeHash($nodeObject) + { + if (!isset($this->nodeHashCallback)) { + return (string) $nodeObject; + } + + return call_user_func($this->nodeHashCallback, $nodeObject); + } + + /** + * {@inheritdoc} + */ + public function hash($value) + { + return crc32($value); + } + + /** + * {@inheritdoc} + */ + public function getByHash($hash) + { + return $this->ring[$this->getSlot($hash)]; + } + + /** + * {@inheritdoc} + */ + public function getBySlot($slot) + { + $this->initialize(); + + if (isset($this->ring[$slot])) { + return $this->ring[$slot]; + } + } + + /** + * {@inheritdoc} + */ + public function getSlot($hash) + { + $this->initialize(); + + $ringKeys = $this->ringKeys; + $upper = $this->ringKeysCount - 1; + $lower = 0; + + while ($lower <= $upper) { + $index = ($lower + $upper) >> 1; + $item = $ringKeys[$index]; + + if ($item > $hash) { + $upper = $index - 1; + } elseif ($item < $hash) { + $lower = $index + 1; + } else { + return $item; + } + } + + return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)]; + } + + /** + * {@inheritdoc} + */ + public function get($value) + { + $hash = $this->hash($value); + $node = $this->getByHash($hash); + + return $node; + } + + /** + * Implements a strategy to deal with wrap-around errors during binary searches. + * + * @param int $upper + * @param int $lower + * @param int $ringKeysCount + * + * @return int + */ + protected function wrapAroundStrategy($upper, $lower, $ringKeysCount) + { + // Binary search for the last item in ringkeys with a value less or + // equal to the key. If no such item exists, return the last item. + return $upper >= 0 ? $upper : $ringKeysCount - 1; + } + + /** + * {@inheritdoc} + */ + public function getHashGenerator() + { + return $this; + } +} diff --git a/vendor/predis/predis/src/Cluster/Distributor/KetamaRing.php b/vendor/predis/predis/src/Cluster/Distributor/KetamaRing.php new file mode 100644 index 0000000..dc77f32 --- /dev/null +++ b/vendor/predis/predis/src/Cluster/Distributor/KetamaRing.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster\Distributor; + +/** + * This class implements an hashring-based distributor that uses the same + * algorithm of libketama to distribute keys in a cluster using client-side + * sharding. + * + * @author Daniele Alessandri + * @author Lorenzo Castelli + */ +class KetamaRing extends HashRing +{ + const DEFAULT_REPLICAS = 160; + + /** + * @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes. + */ + public function __construct($nodeHashCallback = null) + { + parent::__construct($this::DEFAULT_REPLICAS, $nodeHashCallback); + } + + /** + * {@inheritdoc} + */ + protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio) + { + $nodeObject = $node['object']; + $nodeHash = $this->getNodeHash($nodeObject); + $replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4)); + + for ($i = 0; $i < $replicas; ++$i) { + $unpackedDigest = unpack('V4', md5("$nodeHash-$i", true)); + + foreach ($unpackedDigest as $key) { + $ring[$key] = $nodeObject; + } + } + } + + /** + * {@inheritdoc} + */ + public function hash($value) + { + $hash = unpack('V', md5($value, true)); + + return $hash[1]; + } + + /** + * {@inheritdoc} + */ + protected function wrapAroundStrategy($upper, $lower, $ringKeysCount) + { + // Binary search for the first item in ringkeys with a value greater + // or equal to the key. If no such item exists, return the first item. + return $lower < $ringKeysCount ? $lower : 0; + } +} diff --git a/vendor/predis/predis/src/Cluster/Hash/CRC16.php b/vendor/predis/predis/src/Cluster/Hash/CRC16.php new file mode 100644 index 0000000..3add0ce --- /dev/null +++ b/vendor/predis/predis/src/Cluster/Hash/CRC16.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 Predis\Cluster\Hash; + +/** + * Hash generator implementing the CRC-CCITT-16 algorithm used by redis-cluster. + * + * @author Daniele Alessandri + */ +class CRC16 implements HashGeneratorInterface +{ + private static $CCITT_16 = array( + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, + ); + + /** + * {@inheritdoc} + */ + public function hash($value) + { + // CRC-CCITT-16 algorithm + $crc = 0; + $CCITT_16 = self::$CCITT_16; + $strlen = strlen($value); + + for ($i = 0; $i < $strlen; ++$i) { + $crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF; + } + + return $crc; + } +} diff --git a/vendor/predis/predis/src/Cluster/Hash/HashGeneratorInterface.php b/vendor/predis/predis/src/Cluster/Hash/HashGeneratorInterface.php new file mode 100644 index 0000000..271b9e7 --- /dev/null +++ b/vendor/predis/predis/src/Cluster/Hash/HashGeneratorInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster\Hash; + +/** + * An hash generator implements the logic used to calculate the hash of a key to + * distribute operations among Redis nodes. + * + * @author Daniele Alessandri + */ +interface HashGeneratorInterface +{ + /** + * Generates an hash from a string to be used for distribution. + * + * @param string $value String value. + * + * @return int + */ + public function hash($value); +} diff --git a/vendor/predis/predis/src/Cluster/PredisStrategy.php b/vendor/predis/predis/src/Cluster/PredisStrategy.php new file mode 100644 index 0000000..2066842 --- /dev/null +++ b/vendor/predis/predis/src/Cluster/PredisStrategy.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster; + +use Predis\Cluster\Distributor\DistributorInterface; +use Predis\Cluster\Distributor\HashRing; + +/** + * Default cluster strategy used by Predis to handle client-side sharding. + * + * @author Daniele Alessandri + */ +class PredisStrategy extends ClusterStrategy +{ + protected $distributor; + + /** + * @param DistributorInterface $distributor Optional distributor instance. + */ + public function __construct(DistributorInterface $distributor = null) + { + parent::__construct(); + + $this->distributor = $distributor ?: new HashRing(); + } + + /** + * {@inheritdoc} + */ + public function getSlotByKey($key) + { + $key = $this->extractKeyTag($key); + $hash = $this->distributor->hash($key); + $slot = $this->distributor->getSlot($hash); + + return $slot; + } + + /** + * {@inheritdoc} + */ + protected function checkSameSlotForKeys(array $keys) + { + if (!$count = count($keys)) { + return false; + } + + $currentKey = $this->extractKeyTag($keys[0]); + + for ($i = 1; $i < $count; ++$i) { + $nextKey = $this->extractKeyTag($keys[$i]); + + if ($currentKey !== $nextKey) { + return false; + } + + $currentKey = $nextKey; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function getDistributor() + { + return $this->distributor; + } +} diff --git a/vendor/predis/predis/src/Cluster/RedisStrategy.php b/vendor/predis/predis/src/Cluster/RedisStrategy.php new file mode 100644 index 0000000..df0bdb4 --- /dev/null +++ b/vendor/predis/predis/src/Cluster/RedisStrategy.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster; + +use Predis\Cluster\Hash\CRC16; +use Predis\Cluster\Hash\HashGeneratorInterface; +use Predis\NotSupportedException; + +/** + * Default class used by Predis to calculate hashes out of keys of + * commands supported by redis-cluster. + * + * @author Daniele Alessandri + */ +class RedisStrategy extends ClusterStrategy +{ + protected $hashGenerator; + + /** + * @param HashGeneratorInterface $hashGenerator Hash generator instance. + */ + public function __construct(HashGeneratorInterface $hashGenerator = null) + { + parent::__construct(); + + $this->hashGenerator = $hashGenerator ?: new CRC16(); + } + + /** + * {@inheritdoc} + */ + public function getSlotByKey($key) + { + $key = $this->extractKeyTag($key); + $slot = $this->hashGenerator->hash($key) & 0x3FFF; + + return $slot; + } + + /** + * {@inheritdoc} + */ + public function getDistributor() + { + throw new NotSupportedException( + 'This cluster strategy does not provide an external distributor' + ); + } +} diff --git a/vendor/predis/predis/src/Cluster/StrategyInterface.php b/vendor/predis/predis/src/Cluster/StrategyInterface.php new file mode 100644 index 0000000..cdf7d09 --- /dev/null +++ b/vendor/predis/predis/src/Cluster/StrategyInterface.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster; + +use Predis\Cluster\Distributor\DistributorInterface; +use Predis\Command\CommandInterface; + +/** + * Interface for classes defining the strategy used to calculate an hash out of + * keys extracted from supported commands. + * + * This is mostly useful to support clustering via client-side sharding. + * + * @author Daniele Alessandri + */ +interface StrategyInterface +{ + /** + * Returns a slot for the given command used for clustering distribution or + * NULL when this is not possible. + * + * @param CommandInterface $command Command instance. + * + * @return int + */ + public function getSlot(CommandInterface $command); + + /** + * Returns a slot for the given key used for clustering distribution or NULL + * when this is not possible. + * + * @param string $key Key string. + * + * @return int + */ + public function getSlotByKey($key); + + /** + * Returns a distributor instance to be used by the cluster. + * + * @return DistributorInterface + */ + public function getDistributor(); +} diff --git a/vendor/predis/predis/src/Collection/Iterator/CursorBasedIterator.php b/vendor/predis/predis/src/Collection/Iterator/CursorBasedIterator.php new file mode 100644 index 0000000..922883f --- /dev/null +++ b/vendor/predis/predis/src/Collection/Iterator/CursorBasedIterator.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 Predis\Collection\Iterator; + +use Predis\ClientInterface; +use Predis\NotSupportedException; + +/** + * Provides the base implementation for a fully-rewindable PHP iterator that can + * incrementally iterate over cursor-based collections stored on Redis using the + * commands in the `SCAN` family. + * + * Given their incremental nature with multiple fetches, these kind of iterators + * offer limited guarantees about the returned elements because the collection + * can change several times during the iteration process. + * + * @see http://redis.io/commands/scan + * + * @author Daniele Alessandri + */ +abstract class CursorBasedIterator implements \Iterator +{ + protected $client; + protected $match; + protected $count; + + protected $valid; + protected $fetchmore; + protected $elements; + protected $cursor; + protected $position; + protected $current; + + /** + * @param ClientInterface $client Client connected to Redis. + * @param string $match Pattern to match during the server-side iteration. + * @param int $count Hint used by Redis to compute the number of results per iteration. + */ + public function __construct(ClientInterface $client, $match = null, $count = null) + { + $this->client = $client; + $this->match = $match; + $this->count = $count; + + $this->reset(); + } + + /** + * Ensures that the client supports the specified Redis command required to + * fetch elements from the server to perform the iteration. + * + * @param ClientInterface $client Client connected to Redis. + * @param string $commandID Command ID. + * + * @throws NotSupportedException + */ + protected function requiredCommand(ClientInterface $client, $commandID) + { + if (!$client->getProfile()->supportsCommand($commandID)) { + throw new NotSupportedException("The current profile does not support '$commandID'."); + } + } + + /** + * Resets the inner state of the iterator. + */ + protected function reset() + { + $this->valid = true; + $this->fetchmore = true; + $this->elements = array(); + $this->cursor = 0; + $this->position = -1; + $this->current = null; + } + + /** + * Returns an array of options for the `SCAN` command. + * + * @return array + */ + protected function getScanOptions() + { + $options = array(); + + if (strlen($this->match) > 0) { + $options['MATCH'] = $this->match; + } + + if ($this->count > 0) { + $options['COUNT'] = $this->count; + } + + return $options; + } + + /** + * Fetches a new set of elements from the remote collection, effectively + * advancing the iteration process. + * + * @return array + */ + abstract protected function executeCommand(); + + /** + * Populates the local buffer of elements fetched from the server during + * the iteration. + */ + protected function fetch() + { + list($cursor, $elements) = $this->executeCommand(); + + if (!$cursor) { + $this->fetchmore = false; + } + + $this->cursor = $cursor; + $this->elements = $elements; + } + + /** + * Extracts next values for key() and current(). + */ + protected function extractNext() + { + ++$this->position; + $this->current = array_shift($this->elements); + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->reset(); + $this->next(); + } + + /** + * {@inheritdoc} + */ + public function current() + { + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + public function next() + { + tryFetch: { + if (!$this->elements && $this->fetchmore) { + $this->fetch(); + } + + if ($this->elements) { + $this->extractNext(); + } elseif ($this->cursor) { + goto tryFetch; + } else { + $this->valid = false; + } + } + } + + /** + * {@inheritdoc} + */ + public function valid() + { + return $this->valid; + } +} diff --git a/vendor/predis/predis/src/Collection/Iterator/HashKey.php b/vendor/predis/predis/src/Collection/Iterator/HashKey.php new file mode 100644 index 0000000..078625f --- /dev/null +++ b/vendor/predis/predis/src/Collection/Iterator/HashKey.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Collection\Iterator; + +use Predis\ClientInterface; + +/** + * Abstracts the iteration of fields and values of an hash by leveraging the + * HSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator. + * + * @author Daniele Alessandri + * + * @link http://redis.io/commands/scan + */ +class HashKey extends CursorBasedIterator +{ + protected $key; + + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client, $key, $match = null, $count = null) + { + $this->requiredCommand($client, 'HSCAN'); + + parent::__construct($client, $match, $count); + + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + protected function executeCommand() + { + return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions()); + } + + /** + * {@inheritdoc} + */ + protected function extractNext() + { + if ($kv = each($this->elements)) { + $this->position = $kv[0]; + $this->current = $kv[1]; + + unset($this->elements[$this->position]); + } + } +} diff --git a/vendor/predis/predis/src/Collection/Iterator/Keyspace.php b/vendor/predis/predis/src/Collection/Iterator/Keyspace.php new file mode 100644 index 0000000..5d985b9 --- /dev/null +++ b/vendor/predis/predis/src/Collection/Iterator/Keyspace.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Collection\Iterator; + +use Predis\ClientInterface; + +/** + * Abstracts the iteration of the keyspace on a Redis instance by leveraging the + * SCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator. + * + * @author Daniele Alessandri + * + * @link http://redis.io/commands/scan + */ +class Keyspace extends CursorBasedIterator +{ + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client, $match = null, $count = null) + { + $this->requiredCommand($client, 'SCAN'); + + parent::__construct($client, $match, $count); + } + + /** + * {@inheritdoc} + */ + protected function executeCommand() + { + return $this->client->scan($this->cursor, $this->getScanOptions()); + } +} diff --git a/vendor/predis/predis/src/Collection/Iterator/ListKey.php b/vendor/predis/predis/src/Collection/Iterator/ListKey.php new file mode 100644 index 0000000..7a6eb47 --- /dev/null +++ b/vendor/predis/predis/src/Collection/Iterator/ListKey.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Collection\Iterator; + +use Predis\ClientInterface; +use Predis\NotSupportedException; + +/** + * Abstracts the iteration of items stored in a list by leveraging the LRANGE + * command wrapped in a fully-rewindable PHP iterator. + * + * This iterator tries to emulate the behaviour of cursor-based iterators based + * on the SCAN-family of commands introduced in Redis <= 2.8, meaning that due + * to its incremental nature with multiple fetches it can only offer limited + * guarantees on the returned elements because the collection can change several + * times (trimmed, deleted, overwritten) during the iteration process. + * + * @author Daniele Alessandri + * + * @link http://redis.io/commands/lrange + */ +class ListKey implements \Iterator +{ + protected $client; + protected $count; + protected $key; + + protected $valid; + protected $fetchmore; + protected $elements; + protected $position; + protected $current; + + /** + * @param ClientInterface $client Client connected to Redis. + * @param string $key Redis list key. + * @param int $count Number of items retrieved on each fetch operation. + * + * @throws \InvalidArgumentException + */ + public function __construct(ClientInterface $client, $key, $count = 10) + { + $this->requiredCommand($client, 'LRANGE'); + + if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) { + throw new \InvalidArgumentException('The $count argument must be a positive integer.'); + } + + $this->client = $client; + $this->key = $key; + $this->count = $count; + + $this->reset(); + } + + /** + * Ensures that the client instance supports the specified Redis command + * required to fetch elements from the server to perform the iteration. + * + * @param ClientInterface $client Client connected to Redis. + * @param string $commandID Command ID. + * + * @throws NotSupportedException + */ + protected function requiredCommand(ClientInterface $client, $commandID) + { + if (!$client->getProfile()->supportsCommand($commandID)) { + throw new NotSupportedException("The current profile does not support '$commandID'."); + } + } + + /** + * Resets the inner state of the iterator. + */ + protected function reset() + { + $this->valid = true; + $this->fetchmore = true; + $this->elements = array(); + $this->position = -1; + $this->current = null; + } + + /** + * Fetches a new set of elements from the remote collection, effectively + * advancing the iteration process. + * + * @return array + */ + protected function executeCommand() + { + return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count); + } + + /** + * Populates the local buffer of elements fetched from the server during the + * iteration. + */ + protected function fetch() + { + $elements = $this->executeCommand(); + + if (count($elements) < $this->count) { + $this->fetchmore = false; + } + + $this->elements = $elements; + } + + /** + * Extracts next values for key() and current(). + */ + protected function extractNext() + { + ++$this->position; + $this->current = array_shift($this->elements); + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->reset(); + $this->next(); + } + + /** + * {@inheritdoc} + */ + public function current() + { + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + public function next() + { + if (!$this->elements && $this->fetchmore) { + $this->fetch(); + } + + if ($this->elements) { + $this->extractNext(); + } else { + $this->valid = false; + } + } + + /** + * {@inheritdoc} + */ + public function valid() + { + return $this->valid; + } +} diff --git a/vendor/predis/predis/src/Collection/Iterator/SetKey.php b/vendor/predis/predis/src/Collection/Iterator/SetKey.php new file mode 100644 index 0000000..bf25439 --- /dev/null +++ b/vendor/predis/predis/src/Collection/Iterator/SetKey.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 Predis\Collection\Iterator; + +use Predis\ClientInterface; + +/** + * Abstracts the iteration of members stored in a set by leveraging the SSCAN + * command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator. + * + * @author Daniele Alessandri + * + * @link http://redis.io/commands/scan + */ +class SetKey extends CursorBasedIterator +{ + protected $key; + + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client, $key, $match = null, $count = null) + { + $this->requiredCommand($client, 'SSCAN'); + + parent::__construct($client, $match, $count); + + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + protected function executeCommand() + { + return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions()); + } +} diff --git a/vendor/predis/predis/src/Collection/Iterator/SortedSetKey.php b/vendor/predis/predis/src/Collection/Iterator/SortedSetKey.php new file mode 100644 index 0000000..e2f1789 --- /dev/null +++ b/vendor/predis/predis/src/Collection/Iterator/SortedSetKey.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Collection\Iterator; + +use Predis\ClientInterface; + +/** + * Abstracts the iteration of members stored in a sorted set by leveraging the + * ZSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator. + * + * @author Daniele Alessandri + * + * @link http://redis.io/commands/scan + */ +class SortedSetKey extends CursorBasedIterator +{ + protected $key; + + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client, $key, $match = null, $count = null) + { + $this->requiredCommand($client, 'ZSCAN'); + + parent::__construct($client, $match, $count); + + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + protected function executeCommand() + { + return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions()); + } + + /** + * {@inheritdoc} + */ + protected function extractNext() + { + if ($kv = each($this->elements)) { + $this->position = $kv[0]; + $this->current = $kv[1]; + + unset($this->elements[$this->position]); + } + } +} diff --git a/vendor/predis/predis/src/Command/Command.php b/vendor/predis/predis/src/Command/Command.php new file mode 100644 index 0000000..bb538e7 --- /dev/null +++ b/vendor/predis/predis/src/Command/Command.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * Base class for Redis commands. + * + * @author Daniele Alessandri + */ +abstract class Command implements CommandInterface +{ + private $slot; + private $arguments = array(); + + /** + * Returns a filtered array of the arguments. + * + * @param array $arguments List of arguments. + * + * @return array + */ + protected function filterArguments(array $arguments) + { + return $arguments; + } + + /** + * {@inheritdoc} + */ + public function setArguments(array $arguments) + { + $this->arguments = $this->filterArguments($arguments); + unset($this->slot); + } + + /** + * {@inheritdoc} + */ + public function setRawArguments(array $arguments) + { + $this->arguments = $arguments; + unset($this->slot); + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * {@inheritdoc} + */ + public function getArgument($index) + { + if (isset($this->arguments[$index])) { + return $this->arguments[$index]; + } + } + + /** + * {@inheritdoc} + */ + public function setSlot($slot) + { + $this->slot = $slot; + } + + /** + * {@inheritdoc} + */ + public function getSlot() + { + if (isset($this->slot)) { + return $this->slot; + } + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return $data; + } + + /** + * Normalizes the arguments array passed to a Redis command. + * + * @param array $arguments Arguments for a command. + * + * @return array + */ + public static function normalizeArguments(array $arguments) + { + if (count($arguments) === 1 && is_array($arguments[0])) { + return $arguments[0]; + } + + return $arguments; + } + + /** + * Normalizes the arguments array passed to a variadic Redis command. + * + * @param array $arguments Arguments for a command. + * + * @return array + */ + public static function normalizeVariadic(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + return array_merge(array($arguments[0]), $arguments[1]); + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/CommandInterface.php b/vendor/predis/predis/src/Command/CommandInterface.php new file mode 100644 index 0000000..9f349e1 --- /dev/null +++ b/vendor/predis/predis/src/Command/CommandInterface.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 Predis\Command; + +/** + * Defines an abstraction representing a Redis command. + * + * @author Daniele Alessandri + */ +interface CommandInterface +{ + /** + * Returns the ID of the Redis command. By convention, command identifiers + * must always be uppercase. + * + * @return string + */ + public function getId(); + + /** + * Assign the specified slot to the command for clustering distribution. + * + * @param int $slot Slot ID. + */ + public function setSlot($slot); + + /** + * Returns the assigned slot of the command for clustering distribution. + * + * @return int|null + */ + public function getSlot(); + + /** + * Sets the arguments for the command. + * + * @param array $arguments List of arguments. + */ + public function setArguments(array $arguments); + + /** + * Sets the raw arguments for the command without processing them. + * + * @param array $arguments List of arguments. + */ + public function setRawArguments(array $arguments); + + /** + * Gets the arguments of the command. + * + * @return array + */ + public function getArguments(); + + /** + * Gets the argument of the command at the specified index. + * + * @param int $index Index of the desired argument. + * + * @return mixed|null + */ + public function getArgument($index); + + /** + * Parses a raw response and returns a PHP object. + * + * @param string $data Binary string containing the whole response. + * + * @return mixed + */ + public function parseResponse($data); +} diff --git a/vendor/predis/predis/src/Command/ConnectionAuth.php b/vendor/predis/predis/src/Command/ConnectionAuth.php new file mode 100644 index 0000000..c8c9ded --- /dev/null +++ b/vendor/predis/predis/src/Command/ConnectionAuth.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 Predis\Command; + +/** + * @link http://redis.io/commands/auth + * + * @author Daniele Alessandri + */ +class ConnectionAuth extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'AUTH'; + } +} diff --git a/vendor/predis/predis/src/Command/ConnectionEcho.php b/vendor/predis/predis/src/Command/ConnectionEcho.php new file mode 100644 index 0000000..fd49609 --- /dev/null +++ b/vendor/predis/predis/src/Command/ConnectionEcho.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 Predis\Command; + +/** + * @link http://redis.io/commands/echo + * + * @author Daniele Alessandri + */ +class ConnectionEcho extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ECHO'; + } +} diff --git a/vendor/predis/predis/src/Command/ConnectionPing.php b/vendor/predis/predis/src/Command/ConnectionPing.php new file mode 100644 index 0000000..fa9d734 --- /dev/null +++ b/vendor/predis/predis/src/Command/ConnectionPing.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 Predis\Command; + +/** + * @link http://redis.io/commands/ping + * + * @author Daniele Alessandri + */ +class ConnectionPing extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PING'; + } +} diff --git a/vendor/predis/predis/src/Command/ConnectionQuit.php b/vendor/predis/predis/src/Command/ConnectionQuit.php new file mode 100644 index 0000000..e59e31e --- /dev/null +++ b/vendor/predis/predis/src/Command/ConnectionQuit.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 Predis\Command; + +/** + * @link http://redis.io/commands/quit + * + * @author Daniele Alessandri + */ +class ConnectionQuit extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'QUIT'; + } +} diff --git a/vendor/predis/predis/src/Command/ConnectionSelect.php b/vendor/predis/predis/src/Command/ConnectionSelect.php new file mode 100644 index 0000000..1da8256 --- /dev/null +++ b/vendor/predis/predis/src/Command/ConnectionSelect.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 Predis\Command; + +/** + * @link http://redis.io/commands/select + * + * @author Daniele Alessandri + */ +class ConnectionSelect extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SELECT'; + } +} diff --git a/vendor/predis/predis/src/Command/GeospatialGeoAdd.php b/vendor/predis/predis/src/Command/GeospatialGeoAdd.php new file mode 100644 index 0000000..adca2ca --- /dev/null +++ b/vendor/predis/predis/src/Command/GeospatialGeoAdd.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/geoadd + * + * @author Daniele Alessandri + */ +class GeospatialGeoAdd extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GEOADD'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + foreach (array_pop($arguments) as $item) { + $arguments = array_merge($arguments, $item); + } + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/GeospatialGeoDist.php b/vendor/predis/predis/src/Command/GeospatialGeoDist.php new file mode 100644 index 0000000..17c5f54 --- /dev/null +++ b/vendor/predis/predis/src/Command/GeospatialGeoDist.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 Predis\Command; + +/** + * @link http://redis.io/commands/geodist + * + * @author Daniele Alessandri + */ +class GeospatialGeoDist extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GEODIST'; + } +} diff --git a/vendor/predis/predis/src/Command/GeospatialGeoHash.php b/vendor/predis/predis/src/Command/GeospatialGeoHash.php new file mode 100644 index 0000000..2eccaf4 --- /dev/null +++ b/vendor/predis/predis/src/Command/GeospatialGeoHash.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/geohash + * + * @author Daniele Alessandri + */ +class GeospatialGeoHash extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GEOHASH'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + $members = array_pop($arguments); + $arguments = array_merge($arguments, $members); + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/GeospatialGeoPos.php b/vendor/predis/predis/src/Command/GeospatialGeoPos.php new file mode 100644 index 0000000..6b7a9a3 --- /dev/null +++ b/vendor/predis/predis/src/Command/GeospatialGeoPos.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/geopos + * + * @author Daniele Alessandri + */ +class GeospatialGeoPos extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GEOPOS'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + $members = array_pop($arguments); + $arguments = array_merge($arguments, $members); + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/GeospatialGeoRadius.php b/vendor/predis/predis/src/Command/GeospatialGeoRadius.php new file mode 100644 index 0000000..f205214 --- /dev/null +++ b/vendor/predis/predis/src/Command/GeospatialGeoRadius.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/georadius + * + * @author Daniele Alessandri + */ +class GeospatialGeoRadius extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GEORADIUS'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if ($arguments && is_array(end($arguments))) { + $options = array_change_key_case(array_pop($arguments), CASE_UPPER); + + if (isset($options['WITHCOORD']) && $options['WITHCOORD'] == true) { + $arguments[] = 'WITHCOORD'; + } + + if (isset($options['WITHDIST']) && $options['WITHDIST'] == true) { + $arguments[] = 'WITHDIST'; + } + + if (isset($options['WITHHASH']) && $options['WITHHASH'] == true) { + $arguments[] = 'WITHHASH'; + } + + if (isset($options['COUNT'])) { + $arguments[] = 'COUNT'; + $arguments[] = $options['COUNT']; + } + + if (isset($options['SORT'])) { + $arguments[] = strtoupper($options['SORT']); + } + + if (isset($options['STORE'])) { + $arguments[] = 'STORE'; + $arguments[] = $options['STORE']; + } + + if (isset($options['STOREDIST'])) { + $arguments[] = 'STOREDIST'; + $arguments[] = $options['STOREDIST']; + } + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/GeospatialGeoRadiusByMember.php b/vendor/predis/predis/src/Command/GeospatialGeoRadiusByMember.php new file mode 100644 index 0000000..abfff7b --- /dev/null +++ b/vendor/predis/predis/src/Command/GeospatialGeoRadiusByMember.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 Predis\Command; + +/** + * @link http://redis.io/commands/georadiusbymember + * + * @author Daniele Alessandri + */ +class GeospatialGeoRadiusByMember extends GeospatialGeoRadius +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GEORADIUSBYMEMBER'; + } +} diff --git a/vendor/predis/predis/src/Command/HashDelete.php b/vendor/predis/predis/src/Command/HashDelete.php new file mode 100644 index 0000000..d5d4c38 --- /dev/null +++ b/vendor/predis/predis/src/Command/HashDelete.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 Predis\Command; + +/** + * @link http://redis.io/commands/hdel + * + * @author Daniele Alessandri + */ +class HashDelete extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HDEL'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/HashExists.php b/vendor/predis/predis/src/Command/HashExists.php new file mode 100644 index 0000000..ed8dc89 --- /dev/null +++ b/vendor/predis/predis/src/Command/HashExists.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 Predis\Command; + +/** + * @link http://redis.io/commands/hexists + * + * @author Daniele Alessandri + */ +class HashExists extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HEXISTS'; + } +} diff --git a/vendor/predis/predis/src/Command/HashGet.php b/vendor/predis/predis/src/Command/HashGet.php new file mode 100644 index 0000000..20f33da --- /dev/null +++ b/vendor/predis/predis/src/Command/HashGet.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 Predis\Command; + +/** + * @link http://redis.io/commands/hget + * + * @author Daniele Alessandri + */ +class HashGet extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HGET'; + } +} diff --git a/vendor/predis/predis/src/Command/HashGetAll.php b/vendor/predis/predis/src/Command/HashGetAll.php new file mode 100644 index 0000000..d698675 --- /dev/null +++ b/vendor/predis/predis/src/Command/HashGetAll.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hgetall + * + * @author Daniele Alessandri + */ +class HashGetAll extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HGETALL'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + $result = array(); + + for ($i = 0; $i < count($data); ++$i) { + $result[$data[$i]] = $data[++$i]; + } + + return $result; + } +} diff --git a/vendor/predis/predis/src/Command/HashGetMultiple.php b/vendor/predis/predis/src/Command/HashGetMultiple.php new file mode 100644 index 0000000..820ce95 --- /dev/null +++ b/vendor/predis/predis/src/Command/HashGetMultiple.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 Predis\Command; + +/** + * @link http://redis.io/commands/hmget + * + * @author Daniele Alessandri + */ +class HashGetMultiple extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HMGET'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/HashIncrementBy.php b/vendor/predis/predis/src/Command/HashIncrementBy.php new file mode 100644 index 0000000..a37359f --- /dev/null +++ b/vendor/predis/predis/src/Command/HashIncrementBy.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 Predis\Command; + +/** + * @link http://redis.io/commands/hincrby + * + * @author Daniele Alessandri + */ +class HashIncrementBy extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HINCRBY'; + } +} diff --git a/vendor/predis/predis/src/Command/HashIncrementByFloat.php b/vendor/predis/predis/src/Command/HashIncrementByFloat.php new file mode 100644 index 0000000..bce9714 --- /dev/null +++ b/vendor/predis/predis/src/Command/HashIncrementByFloat.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 Predis\Command; + +/** + * @link http://redis.io/commands/hincrbyfloat + * + * @author Daniele Alessandri + */ +class HashIncrementByFloat extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HINCRBYFLOAT'; + } +} diff --git a/vendor/predis/predis/src/Command/HashKeys.php b/vendor/predis/predis/src/Command/HashKeys.php new file mode 100644 index 0000000..2826602 --- /dev/null +++ b/vendor/predis/predis/src/Command/HashKeys.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 Predis\Command; + +/** + * @link http://redis.io/commands/hkeys + * + * @author Daniele Alessandri + */ +class HashKeys extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HKEYS'; + } +} diff --git a/vendor/predis/predis/src/Command/HashLength.php b/vendor/predis/predis/src/Command/HashLength.php new file mode 100644 index 0000000..d70926f --- /dev/null +++ b/vendor/predis/predis/src/Command/HashLength.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 Predis\Command; + +/** + * @link http://redis.io/commands/hlen + * + * @author Daniele Alessandri + */ +class HashLength extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HLEN'; + } +} diff --git a/vendor/predis/predis/src/Command/HashScan.php b/vendor/predis/predis/src/Command/HashScan.php new file mode 100644 index 0000000..afde74e --- /dev/null +++ b/vendor/predis/predis/src/Command/HashScan.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hscan + * + * @author Daniele Alessandri + */ +class HashScan extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HSCAN'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 3 && is_array($arguments[2])) { + $options = $this->prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + + return $arguments; + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $options = array_change_key_case($options, CASE_UPPER); + $normalized = array(); + + if (!empty($options['MATCH'])) { + $normalized[] = 'MATCH'; + $normalized[] = $options['MATCH']; + } + + if (!empty($options['COUNT'])) { + $normalized[] = 'COUNT'; + $normalized[] = $options['COUNT']; + } + + return $normalized; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_array($data)) { + $fields = $data[1]; + $result = array(); + + for ($i = 0; $i < count($fields); ++$i) { + $result[$fields[$i]] = $fields[++$i]; + } + + $data[1] = $result; + } + + return $data; + } +} diff --git a/vendor/predis/predis/src/Command/HashSet.php b/vendor/predis/predis/src/Command/HashSet.php new file mode 100644 index 0000000..cfff3c2 --- /dev/null +++ b/vendor/predis/predis/src/Command/HashSet.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 Predis\Command; + +/** + * @link http://redis.io/commands/hset + * + * @author Daniele Alessandri + */ +class HashSet extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HSET'; + } +} diff --git a/vendor/predis/predis/src/Command/HashSetMultiple.php b/vendor/predis/predis/src/Command/HashSetMultiple.php new file mode 100644 index 0000000..6069e2a --- /dev/null +++ b/vendor/predis/predis/src/Command/HashSetMultiple.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 Predis\Command; + +/** + * @link http://redis.io/commands/hmset + * + * @author Daniele Alessandri + */ +class HashSetMultiple extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HMSET'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + $flattenedKVs = array($arguments[0]); + $args = $arguments[1]; + + foreach ($args as $k => $v) { + $flattenedKVs[] = $k; + $flattenedKVs[] = $v; + } + + return $flattenedKVs; + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/HashSetPreserve.php b/vendor/predis/predis/src/Command/HashSetPreserve.php new file mode 100644 index 0000000..7a29116 --- /dev/null +++ b/vendor/predis/predis/src/Command/HashSetPreserve.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 Predis\Command; + +/** + * @link http://redis.io/commands/hsetnx + * + * @author Daniele Alessandri + */ +class HashSetPreserve extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HSETNX'; + } +} diff --git a/vendor/predis/predis/src/Command/HashStringLength.php b/vendor/predis/predis/src/Command/HashStringLength.php new file mode 100644 index 0000000..7cfda80 --- /dev/null +++ b/vendor/predis/predis/src/Command/HashStringLength.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 Predis\Command; + +/** + * @link http://redis.io/commands/hstrlen + * + * @author Daniele Alessandri + */ +class HashStringLength extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HSTRLEN'; + } +} diff --git a/vendor/predis/predis/src/Command/HashValues.php b/vendor/predis/predis/src/Command/HashValues.php new file mode 100644 index 0000000..0a5ea5f --- /dev/null +++ b/vendor/predis/predis/src/Command/HashValues.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 Predis\Command; + +/** + * @link http://redis.io/commands/hvals + * + * @author Daniele Alessandri + */ +class HashValues extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HVALS'; + } +} diff --git a/vendor/predis/predis/src/Command/HyperLogLogAdd.php b/vendor/predis/predis/src/Command/HyperLogLogAdd.php new file mode 100644 index 0000000..8fe49fc --- /dev/null +++ b/vendor/predis/predis/src/Command/HyperLogLogAdd.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 Predis\Command; + +/** + * @link http://redis.io/commands/pfadd + * + * @author Daniele Alessandri + */ +class HyperLogLogAdd extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PFADD'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/HyperLogLogCount.php b/vendor/predis/predis/src/Command/HyperLogLogCount.php new file mode 100644 index 0000000..0afe542 --- /dev/null +++ b/vendor/predis/predis/src/Command/HyperLogLogCount.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 Predis\Command; + +/** + * @link http://redis.io/commands/pfcount + * + * @author Daniele Alessandri + */ +class HyperLogLogCount extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PFCOUNT'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/HyperLogLogMerge.php b/vendor/predis/predis/src/Command/HyperLogLogMerge.php new file mode 100644 index 0000000..c160be5 --- /dev/null +++ b/vendor/predis/predis/src/Command/HyperLogLogMerge.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 Predis\Command; + +/** + * @link http://redis.io/commands/pfmerge + * + * @author Daniele Alessandri + */ +class HyperLogLogMerge extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PFMERGE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/KeyDelete.php b/vendor/predis/predis/src/Command/KeyDelete.php new file mode 100644 index 0000000..89bdfdb --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyDelete.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 Predis\Command; + +/** + * @link http://redis.io/commands/del + * + * @author Daniele Alessandri + */ +class KeyDelete extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'DEL'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/KeyDump.php b/vendor/predis/predis/src/Command/KeyDump.php new file mode 100644 index 0000000..6d9c488 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyDump.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 Predis\Command; + +/** + * @link http://redis.io/commands/dump + * + * @author Daniele Alessandri + */ +class KeyDump extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'DUMP'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyExists.php b/vendor/predis/predis/src/Command/KeyExists.php new file mode 100644 index 0000000..29e0648 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyExists.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 Predis\Command; + +/** + * @link http://redis.io/commands/exists + * + * @author Daniele Alessandri + */ +class KeyExists extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'EXISTS'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyExpire.php b/vendor/predis/predis/src/Command/KeyExpire.php new file mode 100644 index 0000000..66f4406 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyExpire.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 Predis\Command; + +/** + * @link http://redis.io/commands/expire + * + * @author Daniele Alessandri + */ +class KeyExpire extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'EXPIRE'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyExpireAt.php b/vendor/predis/predis/src/Command/KeyExpireAt.php new file mode 100644 index 0000000..0ae1b2d --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyExpireAt.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 Predis\Command; + +/** + * @link http://redis.io/commands/expireat + * + * @author Daniele Alessandri + */ +class KeyExpireAt extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'EXPIREAT'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyKeys.php b/vendor/predis/predis/src/Command/KeyKeys.php new file mode 100644 index 0000000..6d74c40 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyKeys.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 Predis\Command; + +/** + * @link http://redis.io/commands/keys + * + * @author Daniele Alessandri + */ +class KeyKeys extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'KEYS'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyMigrate.php b/vendor/predis/predis/src/Command/KeyMigrate.php new file mode 100644 index 0000000..3324ef9 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyMigrate.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/migrate + * + * @author Daniele Alessandri + */ +class KeyMigrate extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MIGRATE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (is_array(end($arguments))) { + foreach (array_pop($arguments) as $modifier => $value) { + $modifier = strtoupper($modifier); + + if ($modifier === 'COPY' && $value == true) { + $arguments[] = $modifier; + } + + if ($modifier === 'REPLACE' && $value == true) { + $arguments[] = $modifier; + } + } + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/KeyMove.php b/vendor/predis/predis/src/Command/KeyMove.php new file mode 100644 index 0000000..c849f08 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyMove.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 Predis\Command; + +/** + * @link http://redis.io/commands/move + * + * @author Daniele Alessandri + */ +class KeyMove extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MOVE'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyPersist.php b/vendor/predis/predis/src/Command/KeyPersist.php new file mode 100644 index 0000000..f0cb679 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyPersist.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 Predis\Command; + +/** + * @link http://redis.io/commands/persist + * + * @author Daniele Alessandri + */ +class KeyPersist extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PERSIST'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyPreciseExpire.php b/vendor/predis/predis/src/Command/KeyPreciseExpire.php new file mode 100644 index 0000000..258ec47 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyPreciseExpire.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 Predis\Command; + +/** + * @link http://redis.io/commands/pexpire + * + * @author Daniele Alessandri + */ +class KeyPreciseExpire extends KeyExpire +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PEXPIRE'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyPreciseExpireAt.php b/vendor/predis/predis/src/Command/KeyPreciseExpireAt.php new file mode 100644 index 0000000..e419218 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyPreciseExpireAt.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 Predis\Command; + +/** + * @link http://redis.io/commands/pexpireat + * + * @author Daniele Alessandri + */ +class KeyPreciseExpireAt extends KeyExpireAt +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PEXPIREAT'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyPreciseTimeToLive.php b/vendor/predis/predis/src/Command/KeyPreciseTimeToLive.php new file mode 100644 index 0000000..bdcd34b --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyPreciseTimeToLive.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 Predis\Command; + +/** + * @link http://redis.io/commands/pttl + * + * @author Daniele Alessandri + */ +class KeyPreciseTimeToLive extends KeyTimeToLive +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PTTL'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyRandom.php b/vendor/predis/predis/src/Command/KeyRandom.php new file mode 100644 index 0000000..b208b2d --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyRandom.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 Predis\Command; + +/** + * @link http://redis.io/commands/randomkey + * + * @author Daniele Alessandri + */ +class KeyRandom extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RANDOMKEY'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return $data !== '' ? $data : null; + } +} diff --git a/vendor/predis/predis/src/Command/KeyRename.php b/vendor/predis/predis/src/Command/KeyRename.php new file mode 100644 index 0000000..82e44fb --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyRename.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 Predis\Command; + +/** + * @link http://redis.io/commands/rename + * + * @author Daniele Alessandri + */ +class KeyRename extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RENAME'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyRenamePreserve.php b/vendor/predis/predis/src/Command/KeyRenamePreserve.php new file mode 100644 index 0000000..9793359 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyRenamePreserve.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 Predis\Command; + +/** + * @link http://redis.io/commands/renamenx + * + * @author Daniele Alessandri + */ +class KeyRenamePreserve extends KeyRename +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RENAMENX'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyRestore.php b/vendor/predis/predis/src/Command/KeyRestore.php new file mode 100644 index 0000000..a5b0b2d --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyRestore.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 Predis\Command; + +/** + * @link http://redis.io/commands/restore + * + * @author Daniele Alessandri + */ +class KeyRestore extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RESTORE'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyScan.php b/vendor/predis/predis/src/Command/KeyScan.php new file mode 100644 index 0000000..05f5bb3 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyScan.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/scan + * + * @author Daniele Alessandri + */ +class KeyScan extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SCAN'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + $options = $this->prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + + return $arguments; + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $options = array_change_key_case($options, CASE_UPPER); + $normalized = array(); + + if (!empty($options['MATCH'])) { + $normalized[] = 'MATCH'; + $normalized[] = $options['MATCH']; + } + + if (!empty($options['COUNT'])) { + $normalized[] = 'COUNT'; + $normalized[] = $options['COUNT']; + } + + return $normalized; + } +} diff --git a/vendor/predis/predis/src/Command/KeySort.php b/vendor/predis/predis/src/Command/KeySort.php new file mode 100644 index 0000000..fd449f1 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeySort.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/sort + * + * @author Daniele Alessandri + */ +class KeySort extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SORT'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 1) { + return $arguments; + } + + $query = array($arguments[0]); + $sortParams = array_change_key_case($arguments[1], CASE_UPPER); + + if (isset($sortParams['BY'])) { + $query[] = 'BY'; + $query[] = $sortParams['BY']; + } + + if (isset($sortParams['GET'])) { + $getargs = $sortParams['GET']; + + if (is_array($getargs)) { + foreach ($getargs as $getarg) { + $query[] = 'GET'; + $query[] = $getarg; + } + } else { + $query[] = 'GET'; + $query[] = $getargs; + } + } + + if (isset($sortParams['LIMIT']) && + is_array($sortParams['LIMIT']) && + count($sortParams['LIMIT']) == 2) { + $query[] = 'LIMIT'; + $query[] = $sortParams['LIMIT'][0]; + $query[] = $sortParams['LIMIT'][1]; + } + + if (isset($sortParams['SORT'])) { + $query[] = strtoupper($sortParams['SORT']); + } + + if (isset($sortParams['ALPHA']) && $sortParams['ALPHA'] == true) { + $query[] = 'ALPHA'; + } + + if (isset($sortParams['STORE'])) { + $query[] = 'STORE'; + $query[] = $sortParams['STORE']; + } + + return $query; + } +} diff --git a/vendor/predis/predis/src/Command/KeyTimeToLive.php b/vendor/predis/predis/src/Command/KeyTimeToLive.php new file mode 100644 index 0000000..67697a6 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyTimeToLive.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 Predis\Command; + +/** + * @link http://redis.io/commands/ttl + * + * @author Daniele Alessandri + */ +class KeyTimeToLive extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'TTL'; + } +} diff --git a/vendor/predis/predis/src/Command/KeyType.php b/vendor/predis/predis/src/Command/KeyType.php new file mode 100644 index 0000000..f4f06e4 --- /dev/null +++ b/vendor/predis/predis/src/Command/KeyType.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 Predis\Command; + +/** + * @link http://redis.io/commands/type + * + * @author Daniele Alessandri + */ +class KeyType extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'TYPE'; + } +} diff --git a/vendor/predis/predis/src/Command/ListIndex.php b/vendor/predis/predis/src/Command/ListIndex.php new file mode 100644 index 0000000..27c64be --- /dev/null +++ b/vendor/predis/predis/src/Command/ListIndex.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 Predis\Command; + +/** + * @link http://redis.io/commands/lindex + * + * @author Daniele Alessandri + */ +class ListIndex extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LINDEX'; + } +} diff --git a/vendor/predis/predis/src/Command/ListInsert.php b/vendor/predis/predis/src/Command/ListInsert.php new file mode 100644 index 0000000..7d53d11 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListInsert.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 Predis\Command; + +/** + * @link http://redis.io/commands/linsert + * + * @author Daniele Alessandri + */ +class ListInsert extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LINSERT'; + } +} diff --git a/vendor/predis/predis/src/Command/ListLength.php b/vendor/predis/predis/src/Command/ListLength.php new file mode 100644 index 0000000..6495beb --- /dev/null +++ b/vendor/predis/predis/src/Command/ListLength.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 Predis\Command; + +/** + * @link http://redis.io/commands/llen + * + * @author Daniele Alessandri + */ +class ListLength extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LLEN'; + } +} diff --git a/vendor/predis/predis/src/Command/ListPopFirst.php b/vendor/predis/predis/src/Command/ListPopFirst.php new file mode 100644 index 0000000..84d5d67 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListPopFirst.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 Predis\Command; + +/** + * @link http://redis.io/commands/lpop + * + * @author Daniele Alessandri + */ +class ListPopFirst extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LPOP'; + } +} diff --git a/vendor/predis/predis/src/Command/ListPopFirstBlocking.php b/vendor/predis/predis/src/Command/ListPopFirstBlocking.php new file mode 100644 index 0000000..7dc7c00 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListPopFirstBlocking.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/blpop + * + * @author Daniele Alessandri + */ +class ListPopFirstBlocking extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BLPOP'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[0])) { + list($arguments, $timeout) = $arguments; + array_push($arguments, $timeout); + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/ListPopLast.php b/vendor/predis/predis/src/Command/ListPopLast.php new file mode 100644 index 0000000..9e92db5 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListPopLast.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 Predis\Command; + +/** + * @link http://redis.io/commands/rpop + * + * @author Daniele Alessandri + */ +class ListPopLast extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RPOP'; + } +} diff --git a/vendor/predis/predis/src/Command/ListPopLastBlocking.php b/vendor/predis/predis/src/Command/ListPopLastBlocking.php new file mode 100644 index 0000000..781eb91 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListPopLastBlocking.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 Predis\Command; + +/** + * @link http://redis.io/commands/brpop + * + * @author Daniele Alessandri + */ +class ListPopLastBlocking extends ListPopFirstBlocking +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BRPOP'; + } +} diff --git a/vendor/predis/predis/src/Command/ListPopLastPushHead.php b/vendor/predis/predis/src/Command/ListPopLastPushHead.php new file mode 100644 index 0000000..f430eb2 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListPopLastPushHead.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 Predis\Command; + +/** + * @link http://redis.io/commands/rpoplpush + * + * @author Daniele Alessandri + */ +class ListPopLastPushHead extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RPOPLPUSH'; + } +} diff --git a/vendor/predis/predis/src/Command/ListPopLastPushHeadBlocking.php b/vendor/predis/predis/src/Command/ListPopLastPushHeadBlocking.php new file mode 100644 index 0000000..ee9c93c --- /dev/null +++ b/vendor/predis/predis/src/Command/ListPopLastPushHeadBlocking.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 Predis\Command; + +/** + * @link http://redis.io/commands/brpoplpush + * + * @author Daniele Alessandri + */ +class ListPopLastPushHeadBlocking extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BRPOPLPUSH'; + } +} diff --git a/vendor/predis/predis/src/Command/ListPushHead.php b/vendor/predis/predis/src/Command/ListPushHead.php new file mode 100644 index 0000000..74bf7c4 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListPushHead.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 Predis\Command; + +/** + * @link http://redis.io/commands/lpush + * + * @author Daniele Alessandri + */ +class ListPushHead extends ListPushTail +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LPUSH'; + } +} diff --git a/vendor/predis/predis/src/Command/ListPushHeadX.php b/vendor/predis/predis/src/Command/ListPushHeadX.php new file mode 100644 index 0000000..8e136b8 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListPushHeadX.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 Predis\Command; + +/** + * @link http://redis.io/commands/lpushx + * + * @author Daniele Alessandri + */ +class ListPushHeadX extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LPUSHX'; + } +} diff --git a/vendor/predis/predis/src/Command/ListPushTail.php b/vendor/predis/predis/src/Command/ListPushTail.php new file mode 100644 index 0000000..f2a057c --- /dev/null +++ b/vendor/predis/predis/src/Command/ListPushTail.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 Predis\Command; + +/** + * @link http://redis.io/commands/rpush + * + * @author Daniele Alessandri + */ +class ListPushTail extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RPUSH'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/ListPushTailX.php b/vendor/predis/predis/src/Command/ListPushTailX.php new file mode 100644 index 0000000..1af3645 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListPushTailX.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 Predis\Command; + +/** + * @link http://redis.io/commands/rpushx + * + * @author Daniele Alessandri + */ +class ListPushTailX extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RPUSHX'; + } +} diff --git a/vendor/predis/predis/src/Command/ListRange.php b/vendor/predis/predis/src/Command/ListRange.php new file mode 100644 index 0000000..32a21a6 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListRange.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 Predis\Command; + +/** + * @link http://redis.io/commands/lrange + * + * @author Daniele Alessandri + */ +class ListRange extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LRANGE'; + } +} diff --git a/vendor/predis/predis/src/Command/ListRemove.php b/vendor/predis/predis/src/Command/ListRemove.php new file mode 100644 index 0000000..c580089 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListRemove.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 Predis\Command; + +/** + * @link http://redis.io/commands/lrem + * + * @author Daniele Alessandri + */ +class ListRemove extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LREM'; + } +} diff --git a/vendor/predis/predis/src/Command/ListSet.php b/vendor/predis/predis/src/Command/ListSet.php new file mode 100644 index 0000000..5e59864 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListSet.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 Predis\Command; + +/** + * @link http://redis.io/commands/lset + * + * @author Daniele Alessandri + */ +class ListSet extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LSET'; + } +} diff --git a/vendor/predis/predis/src/Command/ListTrim.php b/vendor/predis/predis/src/Command/ListTrim.php new file mode 100644 index 0000000..1931418 --- /dev/null +++ b/vendor/predis/predis/src/Command/ListTrim.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 Predis\Command; + +/** + * @link http://redis.io/commands/ltrim + * + * @author Daniele Alessandri + */ +class ListTrim extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LTRIM'; + } +} diff --git a/vendor/predis/predis/src/Command/PrefixableCommandInterface.php b/vendor/predis/predis/src/Command/PrefixableCommandInterface.php new file mode 100644 index 0000000..6d54554 --- /dev/null +++ b/vendor/predis/predis/src/Command/PrefixableCommandInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * Defines a command whose keys can be prefixed. + * + * @author Daniele Alessandri + */ +interface PrefixableCommandInterface extends CommandInterface +{ + /** + * Prefixes all the keys found in the arguments of the command. + * + * @param string $prefix String used to prefix the keys. + */ + public function prefixKeys($prefix); +} diff --git a/vendor/predis/predis/src/Command/Processor/KeyPrefixProcessor.php b/vendor/predis/predis/src/Command/Processor/KeyPrefixProcessor.php new file mode 100644 index 0000000..7520127 --- /dev/null +++ b/vendor/predis/predis/src/Command/Processor/KeyPrefixProcessor.php @@ -0,0 +1,450 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Processor; + +use Predis\Command\CommandInterface; +use Predis\Command\PrefixableCommandInterface; + +/** + * Command processor capable of prefixing keys stored in the arguments of Redis + * commands supported. + * + * @author Daniele Alessandri + */ +class KeyPrefixProcessor implements ProcessorInterface +{ + private $prefix; + private $commands; + + /** + * @param string $prefix Prefix for the keys. + */ + public function __construct($prefix) + { + $this->prefix = $prefix; + $this->commands = array( + /* ---------------- Redis 1.2 ---------------- */ + 'EXISTS' => 'static::all', + 'DEL' => 'static::all', + 'TYPE' => 'static::first', + 'KEYS' => 'static::first', + 'RENAME' => 'static::all', + 'RENAMENX' => 'static::all', + 'EXPIRE' => 'static::first', + 'EXPIREAT' => 'static::first', + 'TTL' => 'static::first', + 'MOVE' => 'static::first', + 'SORT' => 'static::sort', + 'DUMP' => 'static::first', + 'RESTORE' => 'static::first', + 'SET' => 'static::first', + 'SETNX' => 'static::first', + 'MSET' => 'static::interleaved', + 'MSETNX' => 'static::interleaved', + 'GET' => 'static::first', + 'MGET' => 'static::all', + 'GETSET' => 'static::first', + 'INCR' => 'static::first', + 'INCRBY' => 'static::first', + 'DECR' => 'static::first', + 'DECRBY' => 'static::first', + 'RPUSH' => 'static::first', + 'LPUSH' => 'static::first', + 'LLEN' => 'static::first', + 'LRANGE' => 'static::first', + 'LTRIM' => 'static::first', + 'LINDEX' => 'static::first', + 'LSET' => 'static::first', + 'LREM' => 'static::first', + 'LPOP' => 'static::first', + 'RPOP' => 'static::first', + 'RPOPLPUSH' => 'static::all', + 'SADD' => 'static::first', + 'SREM' => 'static::first', + 'SPOP' => 'static::first', + 'SMOVE' => 'static::skipLast', + 'SCARD' => 'static::first', + 'SISMEMBER' => 'static::first', + 'SINTER' => 'static::all', + 'SINTERSTORE' => 'static::all', + 'SUNION' => 'static::all', + 'SUNIONSTORE' => 'static::all', + 'SDIFF' => 'static::all', + 'SDIFFSTORE' => 'static::all', + 'SMEMBERS' => 'static::first', + 'SRANDMEMBER' => 'static::first', + 'ZADD' => 'static::first', + 'ZINCRBY' => 'static::first', + 'ZREM' => 'static::first', + 'ZRANGE' => 'static::first', + 'ZREVRANGE' => 'static::first', + 'ZRANGEBYSCORE' => 'static::first', + 'ZCARD' => 'static::first', + 'ZSCORE' => 'static::first', + 'ZREMRANGEBYSCORE' => 'static::first', + /* ---------------- Redis 2.0 ---------------- */ + 'SETEX' => 'static::first', + 'APPEND' => 'static::first', + 'SUBSTR' => 'static::first', + 'BLPOP' => 'static::skipLast', + 'BRPOP' => 'static::skipLast', + 'ZUNIONSTORE' => 'static::zsetStore', + 'ZINTERSTORE' => 'static::zsetStore', + 'ZCOUNT' => 'static::first', + 'ZRANK' => 'static::first', + 'ZREVRANK' => 'static::first', + 'ZREMRANGEBYRANK' => 'static::first', + 'HSET' => 'static::first', + 'HSETNX' => 'static::first', + 'HMSET' => 'static::first', + 'HINCRBY' => 'static::first', + 'HGET' => 'static::first', + 'HMGET' => 'static::first', + 'HDEL' => 'static::first', + 'HEXISTS' => 'static::first', + 'HLEN' => 'static::first', + 'HKEYS' => 'static::first', + 'HVALS' => 'static::first', + 'HGETALL' => 'static::first', + 'SUBSCRIBE' => 'static::all', + 'UNSUBSCRIBE' => 'static::all', + 'PSUBSCRIBE' => 'static::all', + 'PUNSUBSCRIBE' => 'static::all', + 'PUBLISH' => 'static::first', + /* ---------------- Redis 2.2 ---------------- */ + 'PERSIST' => 'static::first', + 'STRLEN' => 'static::first', + 'SETRANGE' => 'static::first', + 'GETRANGE' => 'static::first', + 'SETBIT' => 'static::first', + 'GETBIT' => 'static::first', + 'RPUSHX' => 'static::first', + 'LPUSHX' => 'static::first', + 'LINSERT' => 'static::first', + 'BRPOPLPUSH' => 'static::skipLast', + 'ZREVRANGEBYSCORE' => 'static::first', + 'WATCH' => 'static::all', + /* ---------------- Redis 2.6 ---------------- */ + 'PTTL' => 'static::first', + 'PEXPIRE' => 'static::first', + 'PEXPIREAT' => 'static::first', + 'PSETEX' => 'static::first', + 'INCRBYFLOAT' => 'static::first', + 'BITOP' => 'static::skipFirst', + 'BITCOUNT' => 'static::first', + 'HINCRBYFLOAT' => 'static::first', + 'EVAL' => 'static::evalKeys', + 'EVALSHA' => 'static::evalKeys', + 'MIGRATE' => 'static::migrate', + /* ---------------- Redis 2.8 ---------------- */ + 'SSCAN' => 'static::first', + 'ZSCAN' => 'static::first', + 'HSCAN' => 'static::first', + 'PFADD' => 'static::first', + 'PFCOUNT' => 'static::all', + 'PFMERGE' => 'static::all', + 'ZLEXCOUNT' => 'static::first', + 'ZRANGEBYLEX' => 'static::first', + 'ZREMRANGEBYLEX' => 'static::first', + 'ZREVRANGEBYLEX' => 'static::first', + 'BITPOS' => 'static::first', + /* ---------------- Redis 3.2 ---------------- */ + 'HSTRLEN' => 'static::first', + 'BITFIELD' => 'static::first', + 'GEOADD' => 'static::first', + 'GEOHASH' => 'static::first', + 'GEOPOS' => 'static::first', + 'GEODIST' => 'static::first', + 'GEORADIUS' => 'static::georadius', + 'GEORADIUSBYMEMBER' => 'static::georadius', + ); + } + + /** + * Sets a prefix that is applied to all the keys. + * + * @param string $prefix Prefix for the keys. + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + } + + /** + * Gets the current prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * {@inheritdoc} + */ + public function process(CommandInterface $command) + { + if ($command instanceof PrefixableCommandInterface) { + $command->prefixKeys($this->prefix); + } elseif (isset($this->commands[$commandID = strtoupper($command->getId())])) { + call_user_func($this->commands[$commandID], $command, $this->prefix); + } + } + + /** + * Sets an handler for the specified command ID. + * + * The callback signature must have 2 parameters of the following types: + * + * - Predis\Command\CommandInterface (command instance) + * - String (prefix) + * + * When the callback argument is omitted or NULL, the previously + * associated handler for the specified command ID is removed. + * + * @param string $commandID The ID of the command to be handled. + * @param mixed $callback A valid callable object or NULL. + * + * @throws \InvalidArgumentException + */ + public function setCommandHandler($commandID, $callback = null) + { + $commandID = strtoupper($commandID); + + if (!isset($callback)) { + unset($this->commands[$commandID]); + + return; + } + + if (!is_callable($callback)) { + throw new \InvalidArgumentException( + 'Callback must be a valid callable object or NULL' + ); + } + + $this->commands[$commandID] = $callback; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->getPrefix(); + } + + /** + * Applies the specified prefix only the first argument. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function first(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[0] = "$prefix{$arguments[0]}"; + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to all the arguments. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function all(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + foreach ($arguments as &$key) { + $key = "$prefix$key"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix only to even arguments in the list. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function interleaved(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $length = count($arguments); + + for ($i = 0; $i < $length; $i += 2) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to all the arguments but the first one. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function skipFirst(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $length = count($arguments); + + for ($i = 1; $i < $length; ++$i) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to all the arguments but the last one. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function skipLast(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $length = count($arguments); + + for ($i = 0; $i < $length - 1; ++$i) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the keys of a SORT command. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function sort(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[0] = "$prefix{$arguments[0]}"; + + if (($count = count($arguments)) > 1) { + for ($i = 1; $i < $count; ++$i) { + switch (strtoupper($arguments[$i])) { + case 'BY': + case 'STORE': + $arguments[$i] = "$prefix{$arguments[++$i]}"; + break; + + case 'GET': + $value = $arguments[++$i]; + if ($value !== '#') { + $arguments[$i] = "$prefix$value"; + } + break; + + case 'LIMIT'; + $i += 2; + break; + } + } + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the keys of an EVAL-based command. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function evalKeys(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + for ($i = 2; $i < $arguments[1] + 2; ++$i) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the keys of Z[INTERSECTION|UNION]STORE. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function zsetStore(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[0] = "$prefix{$arguments[0]}"; + $length = ((int) $arguments[1]) + 2; + + for ($i = 2; $i < $length; ++$i) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the key of a MIGRATE command. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function migrate(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[2] = "$prefix{$arguments[2]}"; + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the key of a GEORADIUS command. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function georadius(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[0] = "$prefix{$arguments[0]}"; + $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; + + if (($count = count($arguments)) > $startIndex) { + for ($i = $startIndex; $i < $count; ++$i) { + switch (strtoupper($arguments[$i])) { + case 'STORE': + case 'STOREDIST': + $arguments[$i] = "$prefix{$arguments[++$i]}"; + break; + + } + } + } + + $command->setRawArguments($arguments); + } + } +} diff --git a/vendor/predis/predis/src/Command/Processor/ProcessorChain.php b/vendor/predis/predis/src/Command/Processor/ProcessorChain.php new file mode 100644 index 0000000..0a4768b --- /dev/null +++ b/vendor/predis/predis/src/Command/Processor/ProcessorChain.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Processor; + +use Predis\Command\CommandInterface; + +/** + * Default implementation of a command processors chain. + * + * @author Daniele Alessandri + */ +class ProcessorChain implements \ArrayAccess, ProcessorInterface +{ + private $processors = array(); + + /** + * @param array $processors List of instances of ProcessorInterface. + */ + public function __construct($processors = array()) + { + foreach ($processors as $processor) { + $this->add($processor); + } + } + + /** + * {@inheritdoc} + */ + public function add(ProcessorInterface $processor) + { + $this->processors[] = $processor; + } + + /** + * {@inheritdoc} + */ + public function remove(ProcessorInterface $processor) + { + if (false !== $index = array_search($processor, $this->processors, true)) { + unset($this[$index]); + } + } + + /** + * {@inheritdoc} + */ + public function process(CommandInterface $command) + { + for ($i = 0; $i < $count = count($this->processors); ++$i) { + $this->processors[$i]->process($command); + } + } + + /** + * {@inheritdoc} + */ + public function getProcessors() + { + return $this->processors; + } + + /** + * Returns an iterator over the list of command processor in the chain. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->processors); + } + + /** + * Returns the number of command processors in the chain. + * + * @return int + */ + public function count() + { + return count($this->processors); + } + + /** + * {@inheritdoc} + */ + public function offsetExists($index) + { + return isset($this->processors[$index]); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($index) + { + return $this->processors[$index]; + } + + /** + * {@inheritdoc} + */ + public function offsetSet($index, $processor) + { + if (!$processor instanceof ProcessorInterface) { + throw new \InvalidArgumentException( + 'A processor chain accepts only instances of '. + "'Predis\Command\Processor\ProcessorInterface'." + ); + } + + $this->processors[$index] = $processor; + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($index) + { + unset($this->processors[$index]); + $this->processors = array_values($this->processors); + } +} diff --git a/vendor/predis/predis/src/Command/Processor/ProcessorInterface.php b/vendor/predis/predis/src/Command/Processor/ProcessorInterface.php new file mode 100644 index 0000000..2f91058 --- /dev/null +++ b/vendor/predis/predis/src/Command/Processor/ProcessorInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Processor; + +use Predis\Command\CommandInterface; + +/** + * A command processor processes Redis commands before they are sent to Redis. + * + * @author Daniele Alessandri + */ +interface ProcessorInterface +{ + /** + * Processes the given Redis command. + * + * @param CommandInterface $command Command instance. + */ + public function process(CommandInterface $command); +} diff --git a/vendor/predis/predis/src/Command/PubSubPublish.php b/vendor/predis/predis/src/Command/PubSubPublish.php new file mode 100644 index 0000000..55508f8 --- /dev/null +++ b/vendor/predis/predis/src/Command/PubSubPublish.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 Predis\Command; + +/** + * @link http://redis.io/commands/publish + * + * @author Daniele Alessandri + */ +class PubSubPublish extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PUBLISH'; + } +} diff --git a/vendor/predis/predis/src/Command/PubSubPubsub.php b/vendor/predis/predis/src/Command/PubSubPubsub.php new file mode 100644 index 0000000..8cf8129 --- /dev/null +++ b/vendor/predis/predis/src/Command/PubSubPubsub.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/pubsub + * + * @author Daniele Alessandri + */ +class PubSubPubsub extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PUBSUB'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + switch (strtolower($this->getArgument(0))) { + case 'numsub': + return self::processNumsub($data); + + default: + return $data; + } + } + + /** + * Returns the processed response to PUBSUB NUMSUB. + * + * @param array $channels List of channels + * + * @return array + */ + protected static function processNumsub(array $channels) + { + $processed = array(); + $count = count($channels); + + for ($i = 0; $i < $count; ++$i) { + $processed[$channels[$i]] = $channels[++$i]; + } + + return $processed; + } +} diff --git a/vendor/predis/predis/src/Command/PubSubSubscribe.php b/vendor/predis/predis/src/Command/PubSubSubscribe.php new file mode 100644 index 0000000..e477b31 --- /dev/null +++ b/vendor/predis/predis/src/Command/PubSubSubscribe.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 Predis\Command; + +/** + * @link http://redis.io/commands/subscribe + * + * @author Daniele Alessandri + */ +class PubSubSubscribe extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SUBSCRIBE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/PubSubSubscribeByPattern.php b/vendor/predis/predis/src/Command/PubSubSubscribeByPattern.php new file mode 100644 index 0000000..0118280 --- /dev/null +++ b/vendor/predis/predis/src/Command/PubSubSubscribeByPattern.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 Predis\Command; + +/** + * @link http://redis.io/commands/psubscribe + * + * @author Daniele Alessandri + */ +class PubSubSubscribeByPattern extends PubSubSubscribe +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PSUBSCRIBE'; + } +} diff --git a/vendor/predis/predis/src/Command/PubSubUnsubscribe.php b/vendor/predis/predis/src/Command/PubSubUnsubscribe.php new file mode 100644 index 0000000..d57c3ac --- /dev/null +++ b/vendor/predis/predis/src/Command/PubSubUnsubscribe.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 Predis\Command; + +/** + * @link http://redis.io/commands/unsubscribe + * + * @author Daniele Alessandri + */ +class PubSubUnsubscribe extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'UNSUBSCRIBE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/PubSubUnsubscribeByPattern.php b/vendor/predis/predis/src/Command/PubSubUnsubscribeByPattern.php new file mode 100644 index 0000000..4d76508 --- /dev/null +++ b/vendor/predis/predis/src/Command/PubSubUnsubscribeByPattern.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 Predis\Command; + +/** + * @link http://redis.io/commands/punsubscribe + * + * @author Daniele Alessandri + */ +class PubSubUnsubscribeByPattern extends PubSubUnsubscribe +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PUNSUBSCRIBE'; + } +} diff --git a/vendor/predis/predis/src/Command/RawCommand.php b/vendor/predis/predis/src/Command/RawCommand.php new file mode 100644 index 0000000..2dd48ca --- /dev/null +++ b/vendor/predis/predis/src/Command/RawCommand.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * Class for generic "anonymous" Redis commands. + * + * This command class does not filter input arguments or parse responses, but + * can be used to leverage the standard Predis API to execute any command simply + * by providing the needed arguments following the command signature as defined + * by Redis in its documentation. + * + * @author Daniele Alessandri + */ +class RawCommand implements CommandInterface +{ + private $slot; + private $commandID; + private $arguments; + + /** + * @param array $arguments Command ID and its arguments. + * + * @throws \InvalidArgumentException + */ + public function __construct(array $arguments) + { + if (!$arguments) { + throw new \InvalidArgumentException( + 'The arguments array must contain at least the command ID.' + ); + } + + $this->commandID = strtoupper(array_shift($arguments)); + $this->arguments = $arguments; + } + + /** + * Creates a new raw command using a variadic method. + * + * @param string $commandID Redis command ID. + * @param string ... Arguments list for the command. + * + * @return CommandInterface + */ + public static function create($commandID /* [ $arg, ... */) + { + $arguments = func_get_args(); + $command = new self($arguments); + + return $command; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->commandID; + } + + /** + * {@inheritdoc} + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + unset($this->slot); + } + + /** + * {@inheritdoc} + */ + public function setRawArguments(array $arguments) + { + $this->setArguments($arguments); + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * {@inheritdoc} + */ + public function getArgument($index) + { + if (isset($this->arguments[$index])) { + return $this->arguments[$index]; + } + } + + /** + * {@inheritdoc} + */ + public function setSlot($slot) + { + $this->slot = $slot; + } + + /** + * {@inheritdoc} + */ + public function getSlot() + { + if (isset($this->slot)) { + return $this->slot; + } + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return $data; + } +} diff --git a/vendor/predis/predis/src/Command/ScriptCommand.php b/vendor/predis/predis/src/Command/ScriptCommand.php new file mode 100644 index 0000000..a30bc1d --- /dev/null +++ b/vendor/predis/predis/src/Command/ScriptCommand.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * Base class used to implement an higher level abstraction for commands based + * on Lua scripting with EVAL and EVALSHA. + * + * @link http://redis.io/commands/eval + * + * @author Daniele Alessandri + */ +abstract class ScriptCommand extends ServerEvalSHA +{ + /** + * Gets the body of a Lua script. + * + * @return string + */ + abstract public function getScript(); + + /** + * Specifies the number of arguments that should be considered as keys. + * + * The default behaviour for the base class is to return 0 to indicate that + * all the elements of the arguments array should be considered as keys, but + * subclasses can enforce a static number of keys. + * + * @return int + */ + protected function getKeysCount() + { + return 0; + } + + /** + * Returns the elements from the arguments that are identified as keys. + * + * @return array + */ + public function getKeys() + { + return array_slice($this->getArguments(), 2, $this->getKeysCount()); + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (($numkeys = $this->getKeysCount()) && $numkeys < 0) { + $numkeys = count($arguments) + $numkeys; + } + + return array_merge(array(sha1($this->getScript()), (int) $numkeys), $arguments); + } + + /** + * @return array + */ + public function getEvalArguments() + { + $arguments = $this->getArguments(); + $arguments[0] = $this->getScript(); + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/ServerBackgroundRewriteAOF.php b/vendor/predis/predis/src/Command/ServerBackgroundRewriteAOF.php new file mode 100644 index 0000000..c66a294 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerBackgroundRewriteAOF.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 Predis\Command; + +/** + * @link http://redis.io/commands/bgrewriteaof + * + * @author Daniele Alessandri + */ +class ServerBackgroundRewriteAOF extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BGREWRITEAOF'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return $data == 'Background append only file rewriting started'; + } +} diff --git a/vendor/predis/predis/src/Command/ServerBackgroundSave.php b/vendor/predis/predis/src/Command/ServerBackgroundSave.php new file mode 100644 index 0000000..4bf67ef --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerBackgroundSave.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 Predis\Command; + +/** + * @link http://redis.io/commands/bgsave + * + * @author Daniele Alessandri + */ +class ServerBackgroundSave extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BGSAVE'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return $data === 'Background saving started' ? true : $data; + } +} diff --git a/vendor/predis/predis/src/Command/ServerClient.php b/vendor/predis/predis/src/Command/ServerClient.php new file mode 100644 index 0000000..d00ebbf --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerClient.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/client-list + * @link http://redis.io/commands/client-kill + * @link http://redis.io/commands/client-getname + * @link http://redis.io/commands/client-setname + * + * @author Daniele Alessandri + */ +class ServerClient extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'CLIENT'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + $args = array_change_key_case($this->getArguments(), CASE_UPPER); + + switch (strtoupper($args[0])) { + case 'LIST': + return $this->parseClientList($data); + case 'KILL': + case 'GETNAME': + case 'SETNAME': + default: + return $data; + } + } + + /** + * Parses the response to CLIENT LIST and returns a structured list. + * + * @param string $data Response buffer. + * + * @return array + */ + protected function parseClientList($data) + { + $clients = array(); + + foreach (explode("\n", $data, -1) as $clientData) { + $client = array(); + + foreach (explode(' ', $clientData) as $kv) { + @list($k, $v) = explode('=', $kv); + $client[$k] = $v; + } + + $clients[] = $client; + } + + return $clients; + } +} diff --git a/vendor/predis/predis/src/Command/ServerCommand.php b/vendor/predis/predis/src/Command/ServerCommand.php new file mode 100644 index 0000000..e9b3393 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerCommand.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 Predis\Command; + +/** + * @link http://redis.io/commands/command + * + * @author Daniele Alessandri + */ +class ServerCommand extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'COMMAND'; + } +} diff --git a/vendor/predis/predis/src/Command/ServerConfig.php b/vendor/predis/predis/src/Command/ServerConfig.php new file mode 100644 index 0000000..81e497a --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerConfig.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/config-set + * @link http://redis.io/commands/config-get + * @link http://redis.io/commands/config-resetstat + * @link http://redis.io/commands/config-rewrite + * + * @author Daniele Alessandri + */ +class ServerConfig extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'CONFIG'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_array($data)) { + $result = array(); + + for ($i = 0; $i < count($data); ++$i) { + $result[$data[$i]] = $data[++$i]; + } + + return $result; + } + + return $data; + } +} diff --git a/vendor/predis/predis/src/Command/ServerDatabaseSize.php b/vendor/predis/predis/src/Command/ServerDatabaseSize.php new file mode 100644 index 0000000..6bc8972 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerDatabaseSize.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 Predis\Command; + +/** + * @link http://redis.io/commands/dbsize + * + * @author Daniele Alessandri + */ +class ServerDatabaseSize extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'DBSIZE'; + } +} diff --git a/vendor/predis/predis/src/Command/ServerEval.php b/vendor/predis/predis/src/Command/ServerEval.php new file mode 100644 index 0000000..f5eefd8 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerEval.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/eval + * + * @author Daniele Alessandri + */ +class ServerEval extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'EVAL'; + } + + /** + * Calculates the SHA1 hash of the body of the script. + * + * @return string SHA1 hash. + */ + public function getScriptHash() + { + return sha1($this->getArgument(0)); + } +} diff --git a/vendor/predis/predis/src/Command/ServerEvalSHA.php b/vendor/predis/predis/src/Command/ServerEvalSHA.php new file mode 100644 index 0000000..520a8e9 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerEvalSHA.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/evalsha + * + * @author Daniele Alessandri + */ +class ServerEvalSHA extends ServerEval +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'EVALSHA'; + } + + /** + * Returns the SHA1 hash of the body of the script. + * + * @return string SHA1 hash. + */ + public function getScriptHash() + { + return $this->getArgument(0); + } +} diff --git a/vendor/predis/predis/src/Command/ServerFlushAll.php b/vendor/predis/predis/src/Command/ServerFlushAll.php new file mode 100644 index 0000000..c35b2ad --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerFlushAll.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 Predis\Command; + +/** + * @link http://redis.io/commands/flushall + * + * @author Daniele Alessandri + */ +class ServerFlushAll extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'FLUSHALL'; + } +} diff --git a/vendor/predis/predis/src/Command/ServerFlushDatabase.php b/vendor/predis/predis/src/Command/ServerFlushDatabase.php new file mode 100644 index 0000000..3da6b32 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerFlushDatabase.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 Predis\Command; + +/** + * @link http://redis.io/commands/flushdb + * + * @author Daniele Alessandri + */ +class ServerFlushDatabase extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'FLUSHDB'; + } +} diff --git a/vendor/predis/predis/src/Command/ServerInfo.php b/vendor/predis/predis/src/Command/ServerInfo.php new file mode 100644 index 0000000..96d6ada --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerInfo.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 Predis\Command; + +/** + * @link http://redis.io/commands/info + * + * @author Daniele Alessandri + */ +class ServerInfo extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'INFO'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + $info = array(); + $infoLines = preg_split('/\r?\n/', $data); + + foreach ($infoLines as $row) { + if (strpos($row, ':') === false) { + continue; + } + + list($k, $v) = $this->parseRow($row); + $info[$k] = $v; + } + + return $info; + } + + /** + * Parses a single row of the response and returns the key-value pair. + * + * @param string $row Single row of the response. + * + * @return array + */ + protected function parseRow($row) + { + list($k, $v) = explode(':', $row, 2); + + if (preg_match('/^db\d+$/', $k)) { + $v = $this->parseDatabaseStats($v); + } + + return array($k, $v); + } + + /** + * Extracts the statistics of each logical DB from the string buffer. + * + * @param string $str Response buffer. + * + * @return array + */ + protected function parseDatabaseStats($str) + { + $db = array(); + + foreach (explode(',', $str) as $dbvar) { + list($dbvk, $dbvv) = explode('=', $dbvar); + $db[trim($dbvk)] = $dbvv; + } + + return $db; + } + + /** + * Parses the response and extracts the allocation statistics. + * + * @param string $str Response buffer. + * + * @return array + */ + protected function parseAllocationStats($str) + { + $stats = array(); + + foreach (explode(',', $str) as $kv) { + @list($size, $objects, $extra) = explode('=', $kv); + + // hack to prevent incorrect values when parsing the >=256 key + if (isset($extra)) { + $size = ">=$objects"; + $objects = $extra; + } + + $stats[$size] = $objects; + } + + return $stats; + } +} diff --git a/vendor/predis/predis/src/Command/ServerInfoV26x.php b/vendor/predis/predis/src/Command/ServerInfoV26x.php new file mode 100644 index 0000000..90c9b71 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerInfoV26x.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 Predis\Command; + +/** + * @link http://redis.io/commands/info + * + * @author Daniele Alessandri + */ +class ServerInfoV26x extends ServerInfo +{ + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if ($data === '') { + return array(); + } + + $info = array(); + + $current = null; + $infoLines = preg_split('/\r?\n/', $data); + + if (isset($infoLines[0]) && $infoLines[0][0] !== '#') { + return parent::parseResponse($data); + } + + foreach ($infoLines as $row) { + if ($row === '') { + continue; + } + + if (preg_match('/^# (\w+)$/', $row, $matches)) { + $info[$matches[1]] = array(); + $current = &$info[$matches[1]]; + continue; + } + + list($k, $v) = $this->parseRow($row); + $current[$k] = $v; + } + + return $info; + } +} diff --git a/vendor/predis/predis/src/Command/ServerLastSave.php b/vendor/predis/predis/src/Command/ServerLastSave.php new file mode 100644 index 0000000..feeb19a --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerLastSave.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 Predis\Command; + +/** + * @link http://redis.io/commands/lastsave + * + * @author Daniele Alessandri + */ +class ServerLastSave extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LASTSAVE'; + } +} diff --git a/vendor/predis/predis/src/Command/ServerMonitor.php b/vendor/predis/predis/src/Command/ServerMonitor.php new file mode 100644 index 0000000..1c3d330 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerMonitor.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 Predis\Command; + +/** + * @link http://redis.io/commands/monitor + * + * @author Daniele Alessandri + */ +class ServerMonitor extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MONITOR'; + } +} diff --git a/vendor/predis/predis/src/Command/ServerObject.php b/vendor/predis/predis/src/Command/ServerObject.php new file mode 100644 index 0000000..f921701 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerObject.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 Predis\Command; + +/** + * @link http://redis.io/commands/object + * + * @author Daniele Alessandri + */ +class ServerObject extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'OBJECT'; + } +} diff --git a/vendor/predis/predis/src/Command/ServerSave.php b/vendor/predis/predis/src/Command/ServerSave.php new file mode 100644 index 0000000..addefe2 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerSave.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 Predis\Command; + +/** + * @link http://redis.io/commands/save + * + * @author Daniele Alessandri + */ +class ServerSave extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SAVE'; + } +} diff --git a/vendor/predis/predis/src/Command/ServerScript.php b/vendor/predis/predis/src/Command/ServerScript.php new file mode 100644 index 0000000..7a01018 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerScript.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 Predis\Command; + +/** + * @link http://redis.io/commands/script + * + * @author Daniele Alessandri + */ +class ServerScript extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SCRIPT'; + } +} diff --git a/vendor/predis/predis/src/Command/ServerSentinel.php b/vendor/predis/predis/src/Command/ServerSentinel.php new file mode 100644 index 0000000..c0962db --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerSentinel.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/topics/sentinel + * + * @author Daniele Alessandri + */ +class ServerSentinel extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SENTINEL'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + switch (strtolower($this->getArgument(0))) { + case 'masters': + case 'slaves': + return self::processMastersOrSlaves($data); + + default: + return $data; + } + } + + /** + * Returns a processed response to SENTINEL MASTERS or SENTINEL SLAVES. + * + * @param array $servers List of Redis servers. + * + * @return array + */ + protected static function processMastersOrSlaves(array $servers) + { + foreach ($servers as $idx => $node) { + $processed = array(); + $count = count($node); + + for ($i = 0; $i < $count; ++$i) { + $processed[$node[$i]] = $node[++$i]; + } + + $servers[$idx] = $processed; + } + + return $servers; + } +} diff --git a/vendor/predis/predis/src/Command/ServerShutdown.php b/vendor/predis/predis/src/Command/ServerShutdown.php new file mode 100644 index 0000000..f5b745a --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerShutdown.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 Predis\Command; + +/** + * @link http://redis.io/commands/shutdown + * + * @author Daniele Alessandri + */ +class ServerShutdown extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SHUTDOWN'; + } +} diff --git a/vendor/predis/predis/src/Command/ServerSlaveOf.php b/vendor/predis/predis/src/Command/ServerSlaveOf.php new file mode 100644 index 0000000..4ff4455 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerSlaveOf.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 Predis\Command; + +/** + * @link http://redis.io/commands/slaveof + * + * @author Daniele Alessandri + */ +class ServerSlaveOf extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SLAVEOF'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 0 || $arguments[0] === 'NO ONE') { + return array('NO', 'ONE'); + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/ServerSlowlog.php b/vendor/predis/predis/src/Command/ServerSlowlog.php new file mode 100644 index 0000000..137ff59 --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerSlowlog.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/slowlog + * + * @author Daniele Alessandri + */ +class ServerSlowlog extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SLOWLOG'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_array($data)) { + $log = array(); + + foreach ($data as $index => $entry) { + $log[$index] = array( + 'id' => $entry[0], + 'timestamp' => $entry[1], + 'duration' => $entry[2], + 'command' => $entry[3], + ); + } + + return $log; + } + + return $data; + } +} diff --git a/vendor/predis/predis/src/Command/ServerTime.php b/vendor/predis/predis/src/Command/ServerTime.php new file mode 100644 index 0000000..589f92c --- /dev/null +++ b/vendor/predis/predis/src/Command/ServerTime.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 Predis\Command; + +/** + * @link http://redis.io/commands/time + * + * @author Daniele Alessandri + */ +class ServerTime extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'TIME'; + } +} diff --git a/vendor/predis/predis/src/Command/SetAdd.php b/vendor/predis/predis/src/Command/SetAdd.php new file mode 100644 index 0000000..c118818 --- /dev/null +++ b/vendor/predis/predis/src/Command/SetAdd.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 Predis\Command; + +/** + * @link http://redis.io/commands/sadd + * + * @author Daniele Alessandri + */ +class SetAdd extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SADD'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/SetCardinality.php b/vendor/predis/predis/src/Command/SetCardinality.php new file mode 100644 index 0000000..a9f959b --- /dev/null +++ b/vendor/predis/predis/src/Command/SetCardinality.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 Predis\Command; + +/** + * @link http://redis.io/commands/scard + * + * @author Daniele Alessandri + */ +class SetCardinality extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SCARD'; + } +} diff --git a/vendor/predis/predis/src/Command/SetDifference.php b/vendor/predis/predis/src/Command/SetDifference.php new file mode 100644 index 0000000..35f23f9 --- /dev/null +++ b/vendor/predis/predis/src/Command/SetDifference.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 Predis\Command; + +/** + * @link http://redis.io/commands/sdiff + * + * @author Daniele Alessandri + */ +class SetDifference extends SetIntersection +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SDIFF'; + } +} diff --git a/vendor/predis/predis/src/Command/SetDifferenceStore.php b/vendor/predis/predis/src/Command/SetDifferenceStore.php new file mode 100644 index 0000000..0cb7815 --- /dev/null +++ b/vendor/predis/predis/src/Command/SetDifferenceStore.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 Predis\Command; + +/** + * @link http://redis.io/commands/sdiffstore + * + * @author Daniele Alessandri + */ +class SetDifferenceStore extends SetIntersectionStore +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SDIFFSTORE'; + } +} diff --git a/vendor/predis/predis/src/Command/SetIntersection.php b/vendor/predis/predis/src/Command/SetIntersection.php new file mode 100644 index 0000000..d18258f --- /dev/null +++ b/vendor/predis/predis/src/Command/SetIntersection.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 Predis\Command; + +/** + * @link http://redis.io/commands/sinter + * + * @author Daniele Alessandri + */ +class SetIntersection extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SINTER'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/SetIntersectionStore.php b/vendor/predis/predis/src/Command/SetIntersectionStore.php new file mode 100644 index 0000000..b748618 --- /dev/null +++ b/vendor/predis/predis/src/Command/SetIntersectionStore.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 Predis\Command; + +/** + * @link http://redis.io/commands/sinterstore + * + * @author Daniele Alessandri + */ +class SetIntersectionStore extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SINTERSTORE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + return array_merge(array($arguments[0]), $arguments[1]); + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/SetIsMember.php b/vendor/predis/predis/src/Command/SetIsMember.php new file mode 100644 index 0000000..7742522 --- /dev/null +++ b/vendor/predis/predis/src/Command/SetIsMember.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 Predis\Command; + +/** + * @link http://redis.io/commands/sismember + * + * @author Daniele Alessandri + */ +class SetIsMember extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SISMEMBER'; + } +} diff --git a/vendor/predis/predis/src/Command/SetMembers.php b/vendor/predis/predis/src/Command/SetMembers.php new file mode 100644 index 0000000..f4076ae --- /dev/null +++ b/vendor/predis/predis/src/Command/SetMembers.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 Predis\Command; + +/** + * @link http://redis.io/commands/smembers + * + * @author Daniele Alessandri + */ +class SetMembers extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SMEMBERS'; + } +} diff --git a/vendor/predis/predis/src/Command/SetMove.php b/vendor/predis/predis/src/Command/SetMove.php new file mode 100644 index 0000000..edd4e51 --- /dev/null +++ b/vendor/predis/predis/src/Command/SetMove.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 Predis\Command; + +/** + * @link http://redis.io/commands/smove + * + * @author Daniele Alessandri + */ +class SetMove extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SMOVE'; + } +} diff --git a/vendor/predis/predis/src/Command/SetPop.php b/vendor/predis/predis/src/Command/SetPop.php new file mode 100644 index 0000000..b78d3f3 --- /dev/null +++ b/vendor/predis/predis/src/Command/SetPop.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 Predis\Command; + +/** + * @link http://redis.io/commands/spop + * + * @author Daniele Alessandri + */ +class SetPop extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SPOP'; + } +} diff --git a/vendor/predis/predis/src/Command/SetRandomMember.php b/vendor/predis/predis/src/Command/SetRandomMember.php new file mode 100644 index 0000000..2cb79a0 --- /dev/null +++ b/vendor/predis/predis/src/Command/SetRandomMember.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 Predis\Command; + +/** + * @link http://redis.io/commands/srandmember + * + * @author Daniele Alessandri + */ +class SetRandomMember extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SRANDMEMBER'; + } +} diff --git a/vendor/predis/predis/src/Command/SetRemove.php b/vendor/predis/predis/src/Command/SetRemove.php new file mode 100644 index 0000000..b34710c --- /dev/null +++ b/vendor/predis/predis/src/Command/SetRemove.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 Predis\Command; + +/** + * @link http://redis.io/commands/srem + * + * @author Daniele Alessandri + */ +class SetRemove extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SREM'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/SetScan.php b/vendor/predis/predis/src/Command/SetScan.php new file mode 100644 index 0000000..d42b28d --- /dev/null +++ b/vendor/predis/predis/src/Command/SetScan.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/sscan + * + * @author Daniele Alessandri + */ +class SetScan extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SSCAN'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 3 && is_array($arguments[2])) { + $options = $this->prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + + return $arguments; + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $options = array_change_key_case($options, CASE_UPPER); + $normalized = array(); + + if (!empty($options['MATCH'])) { + $normalized[] = 'MATCH'; + $normalized[] = $options['MATCH']; + } + + if (!empty($options['COUNT'])) { + $normalized[] = 'COUNT'; + $normalized[] = $options['COUNT']; + } + + return $normalized; + } +} diff --git a/vendor/predis/predis/src/Command/SetUnion.php b/vendor/predis/predis/src/Command/SetUnion.php new file mode 100644 index 0000000..7da842b --- /dev/null +++ b/vendor/predis/predis/src/Command/SetUnion.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 Predis\Command; + +/** + * @link http://redis.io/commands/sunion + * + * @author Daniele Alessandri + */ +class SetUnion extends SetIntersection +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SUNION'; + } +} diff --git a/vendor/predis/predis/src/Command/SetUnionStore.php b/vendor/predis/predis/src/Command/SetUnionStore.php new file mode 100644 index 0000000..eac821a --- /dev/null +++ b/vendor/predis/predis/src/Command/SetUnionStore.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 Predis\Command; + +/** + * @link http://redis.io/commands/sunionstore + * + * @author Daniele Alessandri + */ +class SetUnionStore extends SetIntersectionStore +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SUNIONSTORE'; + } +} diff --git a/vendor/predis/predis/src/Command/StringAppend.php b/vendor/predis/predis/src/Command/StringAppend.php new file mode 100644 index 0000000..dac8b84 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringAppend.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 Predis\Command; + +/** + * @link http://redis.io/commands/append + * + * @author Daniele Alessandri + */ +class StringAppend extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'APPEND'; + } +} diff --git a/vendor/predis/predis/src/Command/StringBitCount.php b/vendor/predis/predis/src/Command/StringBitCount.php new file mode 100644 index 0000000..193cce9 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringBitCount.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 Predis\Command; + +/** + * @link http://redis.io/commands/bitcount + * + * @author Daniele Alessandri + */ +class StringBitCount extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BITCOUNT'; + } +} diff --git a/vendor/predis/predis/src/Command/StringBitField.php b/vendor/predis/predis/src/Command/StringBitField.php new file mode 100644 index 0000000..9f4deaa --- /dev/null +++ b/vendor/predis/predis/src/Command/StringBitField.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 Predis\Command; + +/** + * @link http://redis.io/commands/bitfield + * + * @author Daniele Alessandri + */ +class StringBitField extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BITFIELD'; + } +} diff --git a/vendor/predis/predis/src/Command/StringBitOp.php b/vendor/predis/predis/src/Command/StringBitOp.php new file mode 100644 index 0000000..e04ee79 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringBitOp.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/bitop + * + * @author Daniele Alessandri + */ +class StringBitOp extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BITOP'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 3 && is_array($arguments[2])) { + list($operation, $destination) = $arguments; + $arguments = $arguments[2]; + array_unshift($arguments, $operation, $destination); + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/StringBitPos.php b/vendor/predis/predis/src/Command/StringBitPos.php new file mode 100644 index 0000000..4295766 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringBitPos.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 Predis\Command; + +/** + * @link http://redis.io/commands/bitpos + * + * @author Daniele Alessandri + */ +class StringBitPos extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BITPOS'; + } +} diff --git a/vendor/predis/predis/src/Command/StringDecrement.php b/vendor/predis/predis/src/Command/StringDecrement.php new file mode 100644 index 0000000..aa5808c --- /dev/null +++ b/vendor/predis/predis/src/Command/StringDecrement.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 Predis\Command; + +/** + * @link http://redis.io/commands/decr + * + * @author Daniele Alessandri + */ +class StringDecrement extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'DECR'; + } +} diff --git a/vendor/predis/predis/src/Command/StringDecrementBy.php b/vendor/predis/predis/src/Command/StringDecrementBy.php new file mode 100644 index 0000000..cbf3e11 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringDecrementBy.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 Predis\Command; + +/** + * @link http://redis.io/commands/decrby + * + * @author Daniele Alessandri + */ +class StringDecrementBy extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'DECRBY'; + } +} diff --git a/vendor/predis/predis/src/Command/StringGet.php b/vendor/predis/predis/src/Command/StringGet.php new file mode 100644 index 0000000..138e915 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringGet.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 Predis\Command; + +/** + * @link http://redis.io/commands/get + * + * @author Daniele Alessandri + */ +class StringGet extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GET'; + } +} diff --git a/vendor/predis/predis/src/Command/StringGetBit.php b/vendor/predis/predis/src/Command/StringGetBit.php new file mode 100644 index 0000000..3c5b4f9 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringGetBit.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 Predis\Command; + +/** + * @link http://redis.io/commands/getbit + * + * @author Daniele Alessandri + */ +class StringGetBit extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GETBIT'; + } +} diff --git a/vendor/predis/predis/src/Command/StringGetMultiple.php b/vendor/predis/predis/src/Command/StringGetMultiple.php new file mode 100644 index 0000000..e340f9c --- /dev/null +++ b/vendor/predis/predis/src/Command/StringGetMultiple.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 Predis\Command; + +/** + * @link http://redis.io/commands/mget + * + * @author Daniele Alessandri + */ +class StringGetMultiple extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MGET'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/StringGetRange.php b/vendor/predis/predis/src/Command/StringGetRange.php new file mode 100644 index 0000000..bb10565 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringGetRange.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 Predis\Command; + +/** + * @link http://redis.io/commands/getrange + * + * @author Daniele Alessandri + */ +class StringGetRange extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GETRANGE'; + } +} diff --git a/vendor/predis/predis/src/Command/StringGetSet.php b/vendor/predis/predis/src/Command/StringGetSet.php new file mode 100644 index 0000000..b68870d --- /dev/null +++ b/vendor/predis/predis/src/Command/StringGetSet.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 Predis\Command; + +/** + * @link http://redis.io/commands/getset + * + * @author Daniele Alessandri + */ +class StringGetSet extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GETSET'; + } +} diff --git a/vendor/predis/predis/src/Command/StringIncrement.php b/vendor/predis/predis/src/Command/StringIncrement.php new file mode 100644 index 0000000..fa1846e --- /dev/null +++ b/vendor/predis/predis/src/Command/StringIncrement.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 Predis\Command; + +/** + * @link http://redis.io/commands/incr + * + * @author Daniele Alessandri + */ +class StringIncrement extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'INCR'; + } +} diff --git a/vendor/predis/predis/src/Command/StringIncrementBy.php b/vendor/predis/predis/src/Command/StringIncrementBy.php new file mode 100644 index 0000000..9d8241a --- /dev/null +++ b/vendor/predis/predis/src/Command/StringIncrementBy.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 Predis\Command; + +/** + * @link http://redis.io/commands/incrby + * + * @author Daniele Alessandri + */ +class StringIncrementBy extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'INCRBY'; + } +} diff --git a/vendor/predis/predis/src/Command/StringIncrementByFloat.php b/vendor/predis/predis/src/Command/StringIncrementByFloat.php new file mode 100644 index 0000000..164a086 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringIncrementByFloat.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 Predis\Command; + +/** + * @link http://redis.io/commands/incrbyfloat + * + * @author Daniele Alessandri + */ +class StringIncrementByFloat extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'INCRBYFLOAT'; + } +} diff --git a/vendor/predis/predis/src/Command/StringPreciseSetExpire.php b/vendor/predis/predis/src/Command/StringPreciseSetExpire.php new file mode 100644 index 0000000..2faa954 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringPreciseSetExpire.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 Predis\Command; + +/** + * @link http://redis.io/commands/psetex + * + * @author Daniele Alessandri + */ +class StringPreciseSetExpire extends StringSetExpire +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PSETEX'; + } +} diff --git a/vendor/predis/predis/src/Command/StringSet.php b/vendor/predis/predis/src/Command/StringSet.php new file mode 100644 index 0000000..b146994 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringSet.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 Predis\Command; + +/** + * @link http://redis.io/commands/set + * + * @author Daniele Alessandri + */ +class StringSet extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SET'; + } +} diff --git a/vendor/predis/predis/src/Command/StringSetBit.php b/vendor/predis/predis/src/Command/StringSetBit.php new file mode 100644 index 0000000..7933b6b --- /dev/null +++ b/vendor/predis/predis/src/Command/StringSetBit.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 Predis\Command; + +/** + * @link http://redis.io/commands/setbit + * + * @author Daniele Alessandri + */ +class StringSetBit extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SETBIT'; + } +} diff --git a/vendor/predis/predis/src/Command/StringSetExpire.php b/vendor/predis/predis/src/Command/StringSetExpire.php new file mode 100644 index 0000000..f088170 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringSetExpire.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 Predis\Command; + +/** + * @link http://redis.io/commands/setex + * + * @author Daniele Alessandri + */ +class StringSetExpire extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SETEX'; + } +} diff --git a/vendor/predis/predis/src/Command/StringSetMultiple.php b/vendor/predis/predis/src/Command/StringSetMultiple.php new file mode 100644 index 0000000..a3c5324 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringSetMultiple.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 Predis\Command; + +/** + * @link http://redis.io/commands/mset + * + * @author Daniele Alessandri + */ +class StringSetMultiple extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MSET'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 1 && is_array($arguments[0])) { + $flattenedKVs = array(); + $args = $arguments[0]; + + foreach ($args as $k => $v) { + $flattenedKVs[] = $k; + $flattenedKVs[] = $v; + } + + return $flattenedKVs; + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/StringSetMultiplePreserve.php b/vendor/predis/predis/src/Command/StringSetMultiplePreserve.php new file mode 100644 index 0000000..b46a88c --- /dev/null +++ b/vendor/predis/predis/src/Command/StringSetMultiplePreserve.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 Predis\Command; + +/** + * @link http://redis.io/commands/msetnx + * + * @author Daniele Alessandri + */ +class StringSetMultiplePreserve extends StringSetMultiple +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MSETNX'; + } +} diff --git a/vendor/predis/predis/src/Command/StringSetPreserve.php b/vendor/predis/predis/src/Command/StringSetPreserve.php new file mode 100644 index 0000000..e89c974 --- /dev/null +++ b/vendor/predis/predis/src/Command/StringSetPreserve.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 Predis\Command; + +/** + * @link http://redis.io/commands/setnx + * + * @author Daniele Alessandri + */ +class StringSetPreserve extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SETNX'; + } +} diff --git a/vendor/predis/predis/src/Command/StringSetRange.php b/vendor/predis/predis/src/Command/StringSetRange.php new file mode 100644 index 0000000..4d9389f --- /dev/null +++ b/vendor/predis/predis/src/Command/StringSetRange.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 Predis\Command; + +/** + * @link http://redis.io/commands/setrange + * + * @author Daniele Alessandri + */ +class StringSetRange extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SETRANGE'; + } +} diff --git a/vendor/predis/predis/src/Command/StringStrlen.php b/vendor/predis/predis/src/Command/StringStrlen.php new file mode 100644 index 0000000..10f492f --- /dev/null +++ b/vendor/predis/predis/src/Command/StringStrlen.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 Predis\Command; + +/** + * @link http://redis.io/commands/strlen + * + * @author Daniele Alessandri + */ +class StringStrlen extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'STRLEN'; + } +} diff --git a/vendor/predis/predis/src/Command/StringSubstr.php b/vendor/predis/predis/src/Command/StringSubstr.php new file mode 100644 index 0000000..3aab7ad --- /dev/null +++ b/vendor/predis/predis/src/Command/StringSubstr.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 Predis\Command; + +/** + * @link http://redis.io/commands/substr + * + * @author Daniele Alessandri + */ +class StringSubstr extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SUBSTR'; + } +} diff --git a/vendor/predis/predis/src/Command/TransactionDiscard.php b/vendor/predis/predis/src/Command/TransactionDiscard.php new file mode 100644 index 0000000..44aca2b --- /dev/null +++ b/vendor/predis/predis/src/Command/TransactionDiscard.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 Predis\Command; + +/** + * @link http://redis.io/commands/discard + * + * @author Daniele Alessandri + */ +class TransactionDiscard extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'DISCARD'; + } +} diff --git a/vendor/predis/predis/src/Command/TransactionExec.php b/vendor/predis/predis/src/Command/TransactionExec.php new file mode 100644 index 0000000..dbd81aa --- /dev/null +++ b/vendor/predis/predis/src/Command/TransactionExec.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 Predis\Command; + +/** + * @link http://redis.io/commands/exec + * + * @author Daniele Alessandri + */ +class TransactionExec extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'EXEC'; + } +} diff --git a/vendor/predis/predis/src/Command/TransactionMulti.php b/vendor/predis/predis/src/Command/TransactionMulti.php new file mode 100644 index 0000000..673bf55 --- /dev/null +++ b/vendor/predis/predis/src/Command/TransactionMulti.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 Predis\Command; + +/** + * @link http://redis.io/commands/multi + * + * @author Daniele Alessandri + */ +class TransactionMulti extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MULTI'; + } +} diff --git a/vendor/predis/predis/src/Command/TransactionUnwatch.php b/vendor/predis/predis/src/Command/TransactionUnwatch.php new file mode 100644 index 0000000..7925554 --- /dev/null +++ b/vendor/predis/predis/src/Command/TransactionUnwatch.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 Predis\Command; + +/** + * @link http://redis.io/commands/unwatch + * + * @author Daniele Alessandri + */ +class TransactionUnwatch extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'UNWATCH'; + } +} diff --git a/vendor/predis/predis/src/Command/TransactionWatch.php b/vendor/predis/predis/src/Command/TransactionWatch.php new file mode 100644 index 0000000..d360780 --- /dev/null +++ b/vendor/predis/predis/src/Command/TransactionWatch.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 Predis\Command; + +/** + * @link http://redis.io/commands/watch + * + * @author Daniele Alessandri + */ +class TransactionWatch extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'WATCH'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (isset($arguments[0]) && is_array($arguments[0])) { + return $arguments[0]; + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetAdd.php b/vendor/predis/predis/src/Command/ZSetAdd.php new file mode 100644 index 0000000..55e4729 --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetAdd.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zadd + * + * @author Daniele Alessandri + */ +class ZSetAdd extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZADD'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (is_array(end($arguments))) { + foreach (array_pop($arguments) as $member => $score) { + $arguments[] = $score; + $arguments[] = $member; + } + } + + return $arguments; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetCardinality.php b/vendor/predis/predis/src/Command/ZSetCardinality.php new file mode 100644 index 0000000..1033200 --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetCardinality.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 Predis\Command; + +/** + * @link http://redis.io/commands/zcard + * + * @author Daniele Alessandri + */ +class ZSetCardinality extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZCARD'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetCount.php b/vendor/predis/predis/src/Command/ZSetCount.php new file mode 100644 index 0000000..918bd2b --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetCount.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 Predis\Command; + +/** + * @link http://redis.io/commands/zcount + * + * @author Daniele Alessandri + */ +class ZSetCount extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZCOUNT'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetIncrementBy.php b/vendor/predis/predis/src/Command/ZSetIncrementBy.php new file mode 100644 index 0000000..245a8e0 --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetIncrementBy.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 Predis\Command; + +/** + * @link http://redis.io/commands/zincrby + * + * @author Daniele Alessandri + */ +class ZSetIncrementBy extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZINCRBY'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetIntersectionStore.php b/vendor/predis/predis/src/Command/ZSetIntersectionStore.php new file mode 100644 index 0000000..572a7a3 --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetIntersectionStore.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 Predis\Command; + +/** + * @link http://redis.io/commands/zinterstore + * + * @author Daniele Alessandri + */ +class ZSetIntersectionStore extends ZSetUnionStore +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZINTERSTORE'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetLexCount.php b/vendor/predis/predis/src/Command/ZSetLexCount.php new file mode 100644 index 0000000..447b8eb --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetLexCount.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 Predis\Command; + +/** + * @link http://redis.io/commands/zlexcount + * + * @author Daniele Alessandri + */ +class ZSetLexCount extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZLEXCOUNT'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetRange.php b/vendor/predis/predis/src/Command/ZSetRange.php new file mode 100644 index 0000000..ce72c7c --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetRange.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zrange + * + * @author Daniele Alessandri + */ +class ZSetRange extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZRANGE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 4) { + $lastType = gettype($arguments[3]); + + if ($lastType === 'string' && strtoupper($arguments[3]) === 'WITHSCORES') { + // Used for compatibility with older versions + $arguments[3] = array('WITHSCORES' => true); + $lastType = 'array'; + } + + if ($lastType === 'array') { + $options = $this->prepareOptions(array_pop($arguments)); + + return array_merge($arguments, $options); + } + } + + return $arguments; + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $opts = array_change_key_case($options, CASE_UPPER); + $finalizedOpts = array(); + + if (!empty($opts['WITHSCORES'])) { + $finalizedOpts[] = 'WITHSCORES'; + } + + return $finalizedOpts; + } + + /** + * Checks for the presence of the WITHSCORES modifier. + * + * @return bool + */ + protected function withScores() + { + $arguments = $this->getArguments(); + + if (count($arguments) < 4) { + return false; + } + + return strtoupper($arguments[3]) === 'WITHSCORES'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if ($this->withScores()) { + $result = array(); + + for ($i = 0; $i < count($data); ++$i) { + $result[$data[$i]] = $data[++$i]; + } + + return $result; + } + + return $data; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetRangeByLex.php b/vendor/predis/predis/src/Command/ZSetRangeByLex.php new file mode 100644 index 0000000..9b2991a --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetRangeByLex.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 Predis\Command; + +/** + * @link http://redis.io/commands/zrangebylex + * + * @author Daniele Alessandri + */ +class ZSetRangeByLex extends ZSetRange +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZRANGEBYLEX'; + } + + /** + * {@inheritdoc} + */ + protected function prepareOptions($options) + { + $opts = array_change_key_case($options, CASE_UPPER); + $finalizedOpts = array(); + + if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) { + $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER); + + $finalizedOpts[] = 'LIMIT'; + $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0]; + $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1]; + } + + return $finalizedOpts; + } + + /** + * {@inheritdoc} + */ + protected function withScores() + { + return false; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetRangeByScore.php b/vendor/predis/predis/src/Command/ZSetRangeByScore.php new file mode 100644 index 0000000..961a5bc --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetRangeByScore.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 Predis\Command; + +/** + * @link http://redis.io/commands/zrangebyscore + * + * @author Daniele Alessandri + */ +class ZSetRangeByScore extends ZSetRange +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZRANGEBYSCORE'; + } + + /** + * {@inheritdoc} + */ + protected function prepareOptions($options) + { + $opts = array_change_key_case($options, CASE_UPPER); + $finalizedOpts = array(); + + if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) { + $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER); + + $finalizedOpts[] = 'LIMIT'; + $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0]; + $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1]; + } + + return array_merge($finalizedOpts, parent::prepareOptions($options)); + } + + /** + * {@inheritdoc} + */ + protected function withScores() + { + $arguments = $this->getArguments(); + + for ($i = 3; $i < count($arguments); ++$i) { + switch (strtoupper($arguments[$i])) { + case 'WITHSCORES': + return true; + + case 'LIMIT': + $i += 2; + break; + } + } + + return false; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetRank.php b/vendor/predis/predis/src/Command/ZSetRank.php new file mode 100644 index 0000000..d0c9c53 --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetRank.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 Predis\Command; + +/** + * @link http://redis.io/commands/zrank + * + * @author Daniele Alessandri + */ +class ZSetRank extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZRANK'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetRemove.php b/vendor/predis/predis/src/Command/ZSetRemove.php new file mode 100644 index 0000000..cd8ada0 --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetRemove.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 Predis\Command; + +/** + * @link http://redis.io/commands/zrem + * + * @author Daniele Alessandri + */ +class ZSetRemove extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREM'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/vendor/predis/predis/src/Command/ZSetRemoveRangeByLex.php b/vendor/predis/predis/src/Command/ZSetRemoveRangeByLex.php new file mode 100644 index 0000000..9ea2d9e --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetRemoveRangeByLex.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 Predis\Command; + +/** + * @link http://redis.io/commands/zremrangebylex + * + * @author Daniele Alessandri + */ +class ZSetRemoveRangeByLex extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREMRANGEBYLEX'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetRemoveRangeByRank.php b/vendor/predis/predis/src/Command/ZSetRemoveRangeByRank.php new file mode 100644 index 0000000..89cd5ba --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetRemoveRangeByRank.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 Predis\Command; + +/** + * @link http://redis.io/commands/zremrangebyrank + * + * @author Daniele Alessandri + */ +class ZSetRemoveRangeByRank extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREMRANGEBYRANK'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetRemoveRangeByScore.php b/vendor/predis/predis/src/Command/ZSetRemoveRangeByScore.php new file mode 100644 index 0000000..a7c3081 --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetRemoveRangeByScore.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 Predis\Command; + +/** + * @link http://redis.io/commands/zremrangebyscore + * + * @author Daniele Alessandri + */ +class ZSetRemoveRangeByScore extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREMRANGEBYSCORE'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetReverseRange.php b/vendor/predis/predis/src/Command/ZSetReverseRange.php new file mode 100644 index 0000000..6a46a7a --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetReverseRange.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 Predis\Command; + +/** + * @link http://redis.io/commands/zrevrange + * + * @author Daniele Alessandri + */ +class ZSetReverseRange extends ZSetRange +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREVRANGE'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetReverseRangeByLex.php b/vendor/predis/predis/src/Command/ZSetReverseRangeByLex.php new file mode 100644 index 0000000..5dd611d --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetReverseRangeByLex.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 Predis\Command; + +/** + * @link http://redis.io/commands/zrevrangebylex + * + * @author Daniele Alessandri + */ +class ZSetReverseRangeByLex extends ZSetRangeByLex +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREVRANGEBYLEX'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetReverseRangeByScore.php b/vendor/predis/predis/src/Command/ZSetReverseRangeByScore.php new file mode 100644 index 0000000..1078eb7 --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetReverseRangeByScore.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 Predis\Command; + +/** + * @link http://redis.io/commands/zrevrangebyscore + * + * @author Daniele Alessandri + */ +class ZSetReverseRangeByScore extends ZSetRangeByScore +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREVRANGEBYSCORE'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetReverseRank.php b/vendor/predis/predis/src/Command/ZSetReverseRank.php new file mode 100644 index 0000000..33fb815 --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetReverseRank.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 Predis\Command; + +/** + * @link http://redis.io/commands/zrevrank + * + * @author Daniele Alessandri + */ +class ZSetReverseRank extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREVRANK'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetScan.php b/vendor/predis/predis/src/Command/ZSetScan.php new file mode 100644 index 0000000..1dc2352 --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetScan.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zscan + * + * @author Daniele Alessandri + */ +class ZSetScan extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZSCAN'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 3 && is_array($arguments[2])) { + $options = $this->prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + + return $arguments; + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $options = array_change_key_case($options, CASE_UPPER); + $normalized = array(); + + if (!empty($options['MATCH'])) { + $normalized[] = 'MATCH'; + $normalized[] = $options['MATCH']; + } + + if (!empty($options['COUNT'])) { + $normalized[] = 'COUNT'; + $normalized[] = $options['COUNT']; + } + + return $normalized; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_array($data)) { + $members = $data[1]; + $result = array(); + + for ($i = 0; $i < count($members); ++$i) { + $result[$members[$i]] = (float) $members[++$i]; + } + + $data[1] = $result; + } + + return $data; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetScore.php b/vendor/predis/predis/src/Command/ZSetScore.php new file mode 100644 index 0000000..2e7fce8 --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetScore.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 Predis\Command; + +/** + * @link http://redis.io/commands/zscore + * + * @author Daniele Alessandri + */ +class ZSetScore extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZSCORE'; + } +} diff --git a/vendor/predis/predis/src/Command/ZSetUnionStore.php b/vendor/predis/predis/src/Command/ZSetUnionStore.php new file mode 100644 index 0000000..befc5ce --- /dev/null +++ b/vendor/predis/predis/src/Command/ZSetUnionStore.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 Predis\Command; + +/** + * @link http://redis.io/commands/zunionstore + * + * @author Daniele Alessandri + */ +class ZSetUnionStore extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZUNIONSTORE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + $options = array(); + $argc = count($arguments); + + if ($argc > 2 && is_array($arguments[$argc - 1])) { + $options = $this->prepareOptions(array_pop($arguments)); + } + + if (is_array($arguments[1])) { + $arguments = array_merge( + array($arguments[0], count($arguments[1])), + $arguments[1] + ); + } + + return array_merge($arguments, $options); + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + private function prepareOptions($options) + { + $opts = array_change_key_case($options, CASE_UPPER); + $finalizedOpts = array(); + + if (isset($opts['WEIGHTS']) && is_array($opts['WEIGHTS'])) { + $finalizedOpts[] = 'WEIGHTS'; + + foreach ($opts['WEIGHTS'] as $weight) { + $finalizedOpts[] = $weight; + } + } + + if (isset($opts['AGGREGATE'])) { + $finalizedOpts[] = 'AGGREGATE'; + $finalizedOpts[] = $opts['AGGREGATE']; + } + + return $finalizedOpts; + } +} diff --git a/vendor/predis/predis/src/CommunicationException.php b/vendor/predis/predis/src/CommunicationException.php new file mode 100644 index 0000000..13fe357 --- /dev/null +++ b/vendor/predis/predis/src/CommunicationException.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis; + +use Predis\Connection\NodeConnectionInterface; + +/** + * Base exception class for network-related errors. + * + * @author Daniele Alessandri + */ +abstract class CommunicationException extends PredisException +{ + private $connection; + + /** + * @param NodeConnectionInterface $connection Connection that generated the exception. + * @param string $message Error message. + * @param int $code Error code. + * @param \Exception $innerException Inner exception for wrapping the original error. + */ + public function __construct( + NodeConnectionInterface $connection, + $message = null, + $code = null, + \Exception $innerException = null + ) { + parent::__construct($message, $code, $innerException); + $this->connection = $connection; + } + + /** + * Gets the connection that generated the exception. + * + * @return NodeConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Indicates if the receiver should reset the underlying connection. + * + * @return bool + */ + public function shouldResetConnection() + { + return true; + } + + /** + * Helper method to handle exceptions generated by a connection object. + * + * @param CommunicationException $exception Exception. + * + * @throws CommunicationException + */ + public static function handle(CommunicationException $exception) + { + if ($exception->shouldResetConnection()) { + $connection = $exception->getConnection(); + + if ($connection->isConnected()) { + $connection->disconnect(); + } + } + + throw $exception; + } +} diff --git a/vendor/predis/predis/src/Configuration/ClusterOption.php b/vendor/predis/predis/src/Configuration/ClusterOption.php new file mode 100644 index 0000000..69e36de --- /dev/null +++ b/vendor/predis/predis/src/Configuration/ClusterOption.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +use Predis\Connection\Aggregate\ClusterInterface; +use Predis\Connection\Aggregate\PredisCluster; +use Predis\Connection\Aggregate\RedisCluster; + +/** + * Configures an aggregate connection used for clustering + * multiple Redis nodes using various implementations with + * different algorithms or strategies. + * + * @author Daniele Alessandri + */ +class ClusterOption implements OptionInterface +{ + /** + * Creates a new cluster connection from on a known descriptive name. + * + * @param OptionsInterface $options Instance of the client options. + * @param string $id Descriptive identifier of the cluster type (`predis`, `redis-cluster`) + * + * @return ClusterInterface|null + */ + protected function createByDescription(OptionsInterface $options, $id) + { + switch ($id) { + case 'predis': + case 'predis-cluster': + return new PredisCluster(); + + case 'redis': + case 'redis-cluster': + return new RedisCluster($options->connections); + + default: + return; + } + } + + /** + * {@inheritdoc} + */ + public function filter(OptionsInterface $options, $value) + { + if (is_string($value)) { + $value = $this->createByDescription($options, $value); + } + + if (!$value instanceof ClusterInterface) { + throw new \InvalidArgumentException( + "An instance of type 'Predis\Connection\Aggregate\ClusterInterface' was expected." + ); + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + return new PredisCluster(); + } +} diff --git a/vendor/predis/predis/src/Configuration/ConnectionFactoryOption.php b/vendor/predis/predis/src/Configuration/ConnectionFactoryOption.php new file mode 100644 index 0000000..bf8479c --- /dev/null +++ b/vendor/predis/predis/src/Configuration/ConnectionFactoryOption.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +use Predis\Connection\Factory; +use Predis\Connection\FactoryInterface; + +/** + * Configures a connection factory used by the client to create new connection + * instances for single Redis nodes. + * + * @author Daniele Alessandri + */ +class ConnectionFactoryOption implements OptionInterface +{ + /** + * {@inheritdoc} + */ + public function filter(OptionsInterface $options, $value) + { + if ($value instanceof FactoryInterface) { + return $value; + } elseif (is_array($value)) { + $factory = $this->getDefault($options); + + foreach ($value as $scheme => $initializer) { + $factory->define($scheme, $initializer); + } + + return $factory; + } else { + throw new \InvalidArgumentException( + 'Invalid value provided for the connections option.' + ); + } + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + $factory = new Factory(); + + if ($options->defined('parameters')) { + $factory->setDefaultParameters($options->parameters); + } + + return $factory; + } +} diff --git a/vendor/predis/predis/src/Configuration/ExceptionsOption.php b/vendor/predis/predis/src/Configuration/ExceptionsOption.php new file mode 100644 index 0000000..337733e --- /dev/null +++ b/vendor/predis/predis/src/Configuration/ExceptionsOption.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +/** + * Configures whether consumers (such as the client) should throw exceptions on + * Redis errors (-ERR responses) or just return instances of error responses. + * + * @author Daniele Alessandri + */ +class ExceptionsOption implements OptionInterface +{ + /** + * {@inheritdoc} + */ + public function filter(OptionsInterface $options, $value) + { + return filter_var($value, FILTER_VALIDATE_BOOLEAN); + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + return true; + } +} diff --git a/vendor/predis/predis/src/Configuration/OptionInterface.php b/vendor/predis/predis/src/Configuration/OptionInterface.php new file mode 100644 index 0000000..b31e0c9 --- /dev/null +++ b/vendor/predis/predis/src/Configuration/OptionInterface.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 Predis\Configuration; + +/** + * Defines an handler used by Predis\Configuration\Options to filter, validate + * or return default values for a given option. + * + * @author Daniele Alessandri + */ +interface OptionInterface +{ + /** + * Filters and validates the passed value. + * + * @param OptionsInterface $options Options container. + * @param mixed $value Input value. + * + * @return mixed + */ + public function filter(OptionsInterface $options, $value); + + /** + * Returns the default value for the option. + * + * @param OptionsInterface $options Options container. + * + * @return mixed + */ + public function getDefault(OptionsInterface $options); +} diff --git a/vendor/predis/predis/src/Configuration/Options.php b/vendor/predis/predis/src/Configuration/Options.php new file mode 100644 index 0000000..c17dd54 --- /dev/null +++ b/vendor/predis/predis/src/Configuration/Options.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +/** + * Manages Predis options with filtering, conversion and lazy initialization of + * values using a mini-DI container approach. + * + * {@inheritdoc} + * + * @author Daniele Alessandri + */ +class Options implements OptionsInterface +{ + protected $input; + protected $options; + protected $handlers; + + /** + * @param array $options Array of options with their values + */ + public function __construct(array $options = array()) + { + $this->input = $options; + $this->options = array(); + $this->handlers = $this->getHandlers(); + } + + /** + * Ensures that the default options are initialized. + * + * @return array + */ + protected function getHandlers() + { + return array( + 'cluster' => 'Predis\Configuration\ClusterOption', + 'connections' => 'Predis\Configuration\ConnectionFactoryOption', + 'exceptions' => 'Predis\Configuration\ExceptionsOption', + 'prefix' => 'Predis\Configuration\PrefixOption', + 'profile' => 'Predis\Configuration\ProfileOption', + 'replication' => 'Predis\Configuration\ReplicationOption', + ); + } + + /** + * {@inheritdoc} + */ + public function getDefault($option) + { + if (isset($this->handlers[$option])) { + $handler = $this->handlers[$option]; + $handler = new $handler(); + + return $handler->getDefault($this); + } + } + + /** + * {@inheritdoc} + */ + public function defined($option) + { + return + array_key_exists($option, $this->options) || + array_key_exists($option, $this->input) + ; + } + + /** + * {@inheritdoc} + */ + public function __isset($option) + { + return ( + array_key_exists($option, $this->options) || + array_key_exists($option, $this->input) + ) && $this->__get($option) !== null; + } + + /** + * {@inheritdoc} + */ + public function __get($option) + { + if (isset($this->options[$option]) || array_key_exists($option, $this->options)) { + return $this->options[$option]; + } + + if (isset($this->input[$option]) || array_key_exists($option, $this->input)) { + $value = $this->input[$option]; + unset($this->input[$option]); + + if (is_object($value) && method_exists($value, '__invoke')) { + $value = $value($this, $option); + } + + if (isset($this->handlers[$option])) { + $handler = $this->handlers[$option]; + $handler = new $handler(); + $value = $handler->filter($this, $value); + } + + return $this->options[$option] = $value; + } + + if (isset($this->handlers[$option])) { + return $this->options[$option] = $this->getDefault($option); + } + + return; + } +} diff --git a/vendor/predis/predis/src/Configuration/OptionsInterface.php b/vendor/predis/predis/src/Configuration/OptionsInterface.php new file mode 100644 index 0000000..f811647 --- /dev/null +++ b/vendor/predis/predis/src/Configuration/OptionsInterface.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 Predis\Configuration; + +/** + * Interface defining a container for client options. + * + * @property-read mixed aggregate Custom connection aggregator. + * @property-read mixed cluster Aggregate connection for clustering. + * @property-read mixed connections Connection factory. + * @property-read mixed exceptions Toggles exceptions in client for -ERR responses. + * @property-read mixed prefix Key prefixing strategy using the given prefix. + * @property-read mixed profile Server profile. + * @property-read mixed replication Aggregate connection for replication. + * + * @author Daniele Alessandri + */ +interface OptionsInterface +{ + /** + * Returns the default value for the given option. + * + * @param string $option Name of the option. + * + * @return mixed|null + */ + public function getDefault($option); + + /** + * Checks if the given option has been set by the user upon initialization. + * + * @param string $option Name of the option. + * + * @return bool + */ + public function defined($option); + + /** + * Checks if the given option has been set and does not evaluate to NULL. + * + * @param string $option Name of the option. + * + * @return bool + */ + public function __isset($option); + + /** + * Returns the value of the given option. + * + * @param string $option Name of the option. + * + * @return mixed|null + */ + public function __get($option); +} diff --git a/vendor/predis/predis/src/Configuration/PrefixOption.php b/vendor/predis/predis/src/Configuration/PrefixOption.php new file mode 100644 index 0000000..5827cdc --- /dev/null +++ b/vendor/predis/predis/src/Configuration/PrefixOption.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 Predis\Configuration; + +use Predis\Command\Processor\KeyPrefixProcessor; +use Predis\Command\Processor\ProcessorInterface; + +/** + * Configures a command processor that apply the specified prefix string to a + * series of Redis commands considered prefixable. + * + * @author Daniele Alessandri + */ +class PrefixOption implements OptionInterface +{ + /** + * {@inheritdoc} + */ + public function filter(OptionsInterface $options, $value) + { + if ($value instanceof ProcessorInterface) { + return $value; + } + + return new KeyPrefixProcessor($value); + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + // NOOP + } +} diff --git a/vendor/predis/predis/src/Configuration/ProfileOption.php b/vendor/predis/predis/src/Configuration/ProfileOption.php new file mode 100644 index 0000000..864936e --- /dev/null +++ b/vendor/predis/predis/src/Configuration/ProfileOption.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +use Predis\Profile\Factory; +use Predis\Profile\ProfileInterface; +use Predis\Profile\RedisProfile; + +/** + * Configures the server profile to be used by the client to create command + * instances depending on the specified version of the Redis server. + * + * @author Daniele Alessandri + */ +class ProfileOption implements OptionInterface +{ + /** + * Sets the commands processors that need to be applied to the profile. + * + * @param OptionsInterface $options Client options. + * @param ProfileInterface $profile Server profile. + */ + protected function setProcessors(OptionsInterface $options, ProfileInterface $profile) + { + if (isset($options->prefix) && $profile instanceof RedisProfile) { + // NOTE: directly using __get('prefix') is actually a workaround for + // HHVM 2.3.0. It's correct and respects the options interface, it's + // just ugly. We will remove this hack when HHVM will fix re-entrant + // calls to __get() once and for all. + + $profile->setProcessor($options->__get('prefix')); + } + } + + /** + * {@inheritdoc} + */ + public function filter(OptionsInterface $options, $value) + { + if (is_string($value)) { + $value = Factory::get($value); + $this->setProcessors($options, $value); + } elseif (!$value instanceof ProfileInterface) { + throw new \InvalidArgumentException('Invalid value for the profile option.'); + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + $profile = Factory::getDefault(); + $this->setProcessors($options, $profile); + + return $profile; + } +} diff --git a/vendor/predis/predis/src/Configuration/ReplicationOption.php b/vendor/predis/predis/src/Configuration/ReplicationOption.php new file mode 100644 index 0000000..1a34f71 --- /dev/null +++ b/vendor/predis/predis/src/Configuration/ReplicationOption.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +use Predis\Connection\Aggregate\MasterSlaveReplication; +use Predis\Connection\Aggregate\ReplicationInterface; +use Predis\Connection\Aggregate\SentinelReplication; + +/** + * Configures an aggregate connection used for master/slave replication among + * multiple Redis nodes. + * + * @author Daniele Alessandri + */ +class ReplicationOption implements OptionInterface +{ + /** + * {@inheritdoc} + * + * @todo There's more code than needed due to a bug in filter_var() as + * discussed here https://bugs.php.net/bug.php?id=49510 and different + * behaviours when encountering NULL values on PHP 5.3. + */ + public function filter(OptionsInterface $options, $value) + { + if ($value instanceof ReplicationInterface) { + return $value; + } + + if (is_bool($value) || $value === null) { + return $value ? $this->getDefault($options) : null; + } + + if ($value === 'sentinel') { + return function ($sentinels, $options) { + return new SentinelReplication($options->service, $sentinels, $options->connections); + }; + } + + if ( + !is_object($value) && + null !== $asbool = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) + ) { + return $asbool ? $this->getDefault($options) : null; + } + + throw new \InvalidArgumentException( + "An instance of type 'Predis\Connection\Aggregate\ReplicationInterface' was expected." + ); + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + $replication = new MasterSlaveReplication(); + + if ($options->autodiscovery) { + $replication->setConnectionFactory($options->connections); + $replication->setAutoDiscovery(true); + } + + return $replication; + } +} diff --git a/vendor/predis/predis/src/Connection/AbstractConnection.php b/vendor/predis/predis/src/Connection/AbstractConnection.php new file mode 100644 index 0000000..fb86513 --- /dev/null +++ b/vendor/predis/predis/src/Connection/AbstractConnection.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; +use Predis\CommunicationException; +use Predis\Protocol\ProtocolException; + +/** + * Base class with the common logic used by connection classes to communicate + * with Redis. + * + * @author Daniele Alessandri + */ +abstract class AbstractConnection implements NodeConnectionInterface +{ + private $resource; + private $cachedId; + + protected $parameters; + protected $initCommands = array(); + + /** + * @param ParametersInterface $parameters Initialization parameters for the connection. + */ + public function __construct(ParametersInterface $parameters) + { + $this->parameters = $this->assertParameters($parameters); + } + + /** + * Disconnects from the server and destroys the underlying resource when + * PHP's garbage collector kicks in. + */ + public function __destruct() + { + $this->disconnect(); + } + + /** + * Checks some of the parameters used to initialize the connection. + * + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @throws \InvalidArgumentException + * + * @return ParametersInterface + */ + abstract protected function assertParameters(ParametersInterface $parameters); + + /** + * Creates the underlying resource used to communicate with Redis. + * + * @return mixed + */ + abstract protected function createResource(); + + /** + * {@inheritdoc} + */ + public function isConnected() + { + return isset($this->resource); + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (!$this->isConnected()) { + $this->resource = $this->createResource(); + + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + unset($this->resource); + } + + /** + * {@inheritdoc} + */ + public function addConnectCommand(CommandInterface $command) + { + $this->initCommands[] = $command; + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + $this->writeRequest($command); + + return $this->readResponse($command); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->read(); + } + + /** + * Helper method that returns an exception message augmented with useful + * details from the connection parameters. + * + * @param string $message Error message. + * + * @return string + */ + private function createExceptionMessage($message) + { + $parameters = $this->parameters; + + if ($parameters->scheme === 'unix') { + return "$message [$parameters->scheme:$parameters->path]"; + } + + if (filter_var($parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + return "$message [$parameters->scheme://[$parameters->host]:$parameters->port]"; + } + + return "$message [$parameters->scheme://$parameters->host:$parameters->port]"; + } + + /** + * Helper method to handle connection errors. + * + * @param string $message Error message. + * @param int $code Error code. + */ + protected function onConnectionError($message, $code = null) + { + CommunicationException::handle( + new ConnectionException($this, static::createExceptionMessage($message), $code) + ); + } + + /** + * Helper method to handle protocol errors. + * + * @param string $message Error message. + */ + protected function onProtocolError($message) + { + CommunicationException::handle( + new ProtocolException($this, static::createExceptionMessage($message)) + ); + } + + /** + * {@inheritdoc} + */ + public function getResource() + { + if (isset($this->resource)) { + return $this->resource; + } + + $this->connect(); + + return $this->resource; + } + + /** + * {@inheritdoc} + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Gets an identifier for the connection. + * + * @return string + */ + protected function getIdentifier() + { + if ($this->parameters->scheme === 'unix') { + return $this->parameters->path; + } + + return "{$this->parameters->host}:{$this->parameters->port}"; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + if (!isset($this->cachedId)) { + $this->cachedId = $this->getIdentifier(); + } + + return $this->cachedId; + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array('parameters', 'initCommands'); + } +} diff --git a/vendor/predis/predis/src/Connection/Aggregate/ClusterInterface.php b/vendor/predis/predis/src/Connection/Aggregate/ClusterInterface.php new file mode 100644 index 0000000..af0f5aa --- /dev/null +++ b/vendor/predis/predis/src/Connection/Aggregate/ClusterInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection\Aggregate; + +use Predis\Connection\AggregateConnectionInterface; + +/** + * Defines a cluster of Redis servers formed by aggregating multiple connection + * instances to single Redis nodes. + * + * @author Daniele Alessandri + */ +interface ClusterInterface extends AggregateConnectionInterface +{ +} diff --git a/vendor/predis/predis/src/Connection/Aggregate/MasterSlaveReplication.php b/vendor/predis/predis/src/Connection/Aggregate/MasterSlaveReplication.php new file mode 100644 index 0000000..238cf2c --- /dev/null +++ b/vendor/predis/predis/src/Connection/Aggregate/MasterSlaveReplication.php @@ -0,0 +1,509 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection\Aggregate; + +use Predis\ClientException; +use Predis\Command\CommandInterface; +use Predis\Command\RawCommand; +use Predis\Connection\ConnectionException; +use Predis\Connection\FactoryInterface; +use Predis\Connection\NodeConnectionInterface; +use Predis\Replication\MissingMasterException; +use Predis\Replication\ReplicationStrategy; +use Predis\Response\ErrorInterface as ResponseErrorInterface; + +/** + * Aggregate connection handling replication of Redis nodes configured in a + * single master / multiple slaves setup. + * + * @author Daniele Alessandri + */ +class MasterSlaveReplication implements ReplicationInterface +{ + /** + * @var ReplicationStrategy + */ + protected $strategy; + + /** + * @var NodeConnectionInterface + */ + protected $master; + + /** + * @var NodeConnectionInterface[] + */ + protected $slaves = array(); + + /** + * @var NodeConnectionInterface + */ + protected $current; + + /** + * @var bool + */ + protected $autoDiscovery = false; + + /** + * @var FactoryInterface + */ + protected $connectionFactory; + + /** + * {@inheritdoc} + */ + public function __construct(ReplicationStrategy $strategy = null) + { + $this->strategy = $strategy ?: new ReplicationStrategy(); + } + + /** + * Configures the automatic discovery of the replication configuration on failure. + * + * @param bool $value Enable or disable auto discovery. + */ + public function setAutoDiscovery($value) + { + if (!$this->connectionFactory) { + throw new ClientException('Automatic discovery requires a connection factory'); + } + + $this->autoDiscovery = (bool) $value; + } + + /** + * Sets the connection factory used to create the connections by the auto + * discovery procedure. + * + * @param FactoryInterface $connectionFactory Connection factory instance. + */ + public function setConnectionFactory(FactoryInterface $connectionFactory) + { + $this->connectionFactory = $connectionFactory; + } + + /** + * Resets the connection state. + */ + protected function reset() + { + $this->current = null; + } + + /** + * {@inheritdoc} + */ + public function add(NodeConnectionInterface $connection) + { + $alias = $connection->getParameters()->alias; + + if ($alias === 'master') { + $this->master = $connection; + } else { + $this->slaves[$alias ?: "slave-$connection"] = $connection; + } + + $this->reset(); + } + + /** + * {@inheritdoc} + */ + public function remove(NodeConnectionInterface $connection) + { + if ($connection->getParameters()->alias === 'master') { + $this->master = null; + $this->reset(); + + return true; + } else { + if (($id = array_search($connection, $this->slaves, true)) !== false) { + unset($this->slaves[$id]); + $this->reset(); + + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getConnection(CommandInterface $command) + { + if (!$this->current) { + if ($this->strategy->isReadOperation($command) && $slave = $this->pickSlave()) { + $this->current = $slave; + } else { + $this->current = $this->getMasterOrDie(); + } + + return $this->current; + } + + if ($this->current === $master = $this->getMasterOrDie()) { + return $master; + } + + if (!$this->strategy->isReadOperation($command) || !$this->slaves) { + $this->current = $master; + } + + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function getConnectionById($connectionId) + { + if ($connectionId === 'master') { + return $this->master; + } + + if (isset($this->slaves[$connectionId])) { + return $this->slaves[$connectionId]; + } + + return; + } + + /** + * {@inheritdoc} + */ + public function switchTo($connection) + { + if (!$connection instanceof NodeConnectionInterface) { + $connection = $this->getConnectionById($connection); + } + + if (!$connection) { + throw new \InvalidArgumentException('Invalid connection or connection not found.'); + } + + if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) { + throw new \InvalidArgumentException('Invalid connection or connection not found.'); + } + + $this->current = $connection; + } + + /** + * Switches to the master server. + */ + public function switchToMaster() + { + $this->switchTo('master'); + } + + /** + * Switches to a random slave server. + */ + public function switchToSlave() + { + $connection = $this->pickSlave(); + $this->switchTo($connection); + } + + /** + * {@inheritdoc} + */ + public function getCurrent() + { + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function getMaster() + { + return $this->master; + } + + /** + * Returns the connection associated to the master server. + * + * @return NodeConnectionInterface + */ + private function getMasterOrDie() + { + if (!$connection = $this->getMaster()) { + throw new MissingMasterException('No master server available for replication'); + } + + return $connection; + } + + /** + * {@inheritdoc} + */ + public function getSlaves() + { + return array_values($this->slaves); + } + + /** + * Returns the underlying replication strategy. + * + * @return ReplicationStrategy + */ + public function getReplicationStrategy() + { + return $this->strategy; + } + + /** + * Returns a random slave. + * + * @return NodeConnectionInterface + */ + protected function pickSlave() + { + if ($this->slaves) { + return $this->slaves[array_rand($this->slaves)]; + } + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + return $this->current ? $this->current->isConnected() : false; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (!$this->current) { + if (!$this->current = $this->pickSlave()) { + if (!$this->current = $this->getMaster()) { + throw new ClientException('No available connection for replication'); + } + } + } + + $this->current->connect(); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + if ($this->master) { + $this->master->disconnect(); + } + + foreach ($this->slaves as $connection) { + $connection->disconnect(); + } + } + + /** + * Handles response from INFO. + * + * @param string $response + * + * @return array + */ + private function handleInfoResponse($response) + { + $info = array(); + + foreach (preg_split('/\r?\n/', $response) as $row) { + if (strpos($row, ':') === false) { + continue; + } + + list($k, $v) = explode(':', $row, 2); + $info[$k] = $v; + } + + return $info; + } + + /** + * Fetches the replication configuration from one of the servers. + */ + public function discover() + { + if (!$this->connectionFactory) { + throw new ClientException('Discovery requires a connection factory'); + } + + RETRY_FETCH: { + try { + if ($connection = $this->getMaster()) { + $this->discoverFromMaster($connection, $this->connectionFactory); + } elseif ($connection = $this->pickSlave()) { + $this->discoverFromSlave($connection, $this->connectionFactory); + } else { + throw new ClientException('No connection available for discovery'); + } + } catch (ConnectionException $exception) { + $this->remove($connection); + goto RETRY_FETCH; + } + } + } + + /** + * Discovers the replication configuration by contacting the master node. + * + * @param NodeConnectionInterface $connection Connection to the master node. + * @param FactoryInterface $connectionFactory Connection factory instance. + */ + protected function discoverFromMaster(NodeConnectionInterface $connection, FactoryInterface $connectionFactory) + { + $response = $connection->executeCommand(RawCommand::create('INFO', 'REPLICATION')); + $replication = $this->handleInfoResponse($response); + + if ($replication['role'] !== 'master') { + throw new ClientException("Role mismatch (expected master, got slave) [$connection]"); + } + + $this->slaves = array(); + + foreach ($replication as $k => $v) { + $parameters = null; + + if (strpos($k, 'slave') === 0 && preg_match('/ip=(?P.*),port=(?P\d+)/', $v, $parameters)) { + $slaveConnection = $connectionFactory->create(array( + 'host' => $parameters['host'], + 'port' => $parameters['port'], + )); + + $this->add($slaveConnection); + } + } + } + + /** + * Discovers the replication configuration by contacting one of the slaves. + * + * @param NodeConnectionInterface $connection Connection to one of the slaves. + * @param FactoryInterface $connectionFactory Connection factory instance. + */ + protected function discoverFromSlave(NodeConnectionInterface $connection, FactoryInterface $connectionFactory) + { + $response = $connection->executeCommand(RawCommand::create('INFO', 'REPLICATION')); + $replication = $this->handleInfoResponse($response); + + if ($replication['role'] !== 'slave') { + throw new ClientException("Role mismatch (expected slave, got master) [$connection]"); + } + + $masterConnection = $connectionFactory->create(array( + 'host' => $replication['master_host'], + 'port' => $replication['master_port'], + 'alias' => 'master', + )); + + $this->add($masterConnection); + + $this->discoverFromMaster($masterConnection, $connectionFactory); + } + + /** + * Retries the execution of a command upon slave failure. + * + * @param CommandInterface $command Command instance. + * @param string $method Actual method. + * + * @return mixed + */ + private function retryCommandOnFailure(CommandInterface $command, $method) + { + RETRY_COMMAND: { + try { + $connection = $this->getConnection($command); + $response = $connection->$method($command); + + if ($response instanceof ResponseErrorInterface && $response->getErrorType() === 'LOADING') { + throw new ConnectionException($connection, "Redis is loading the dataset in memory [$connection]"); + } + } catch (ConnectionException $exception) { + $connection = $exception->getConnection(); + $connection->disconnect(); + + if ($connection === $this->master && !$this->autoDiscovery) { + // Throw immediately when master connection is failing, even + // when the command represents a read-only operation, unless + // automatic discovery has been enabled. + throw $exception; + } else { + // Otherwise remove the failing slave and attempt to execute + // the command again on one of the remaining slaves... + $this->remove($connection); + } + + // ... that is, unless we have no more connections to use. + if (!$this->slaves && !$this->master) { + throw $exception; + } elseif ($this->autoDiscovery) { + $this->discover(); + } + + goto RETRY_COMMAND; + } catch (MissingMasterException $exception) { + if ($this->autoDiscovery) { + $this->discover(); + } else { + throw $exception; + } + + goto RETRY_COMMAND; + } + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + return $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array('master', 'slaves', 'strategy'); + } +} diff --git a/vendor/predis/predis/src/Connection/Aggregate/PredisCluster.php b/vendor/predis/predis/src/Connection/Aggregate/PredisCluster.php new file mode 100644 index 0000000..33f98bf --- /dev/null +++ b/vendor/predis/predis/src/Connection/Aggregate/PredisCluster.php @@ -0,0 +1,235 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection\Aggregate; + +use Predis\Cluster\PredisStrategy; +use Predis\Cluster\StrategyInterface; +use Predis\Command\CommandInterface; +use Predis\Connection\NodeConnectionInterface; +use Predis\NotSupportedException; + +/** + * Abstraction for a cluster of aggregate connections to various Redis servers + * implementing client-side sharding based on pluggable distribution strategies. + * + * @author Daniele Alessandri + * + * @todo Add the ability to remove connections from pool. + */ +class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable +{ + private $pool; + private $strategy; + private $distributor; + + /** + * @param StrategyInterface $strategy Optional cluster strategy. + */ + public function __construct(StrategyInterface $strategy = null) + { + $this->pool = array(); + $this->strategy = $strategy ?: new PredisStrategy(); + $this->distributor = $this->strategy->getDistributor(); + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + foreach ($this->pool as $connection) { + if ($connection->isConnected()) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + foreach ($this->pool as $connection) { + $connection->connect(); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + foreach ($this->pool as $connection) { + $connection->disconnect(); + } + } + + /** + * {@inheritdoc} + */ + public function add(NodeConnectionInterface $connection) + { + $parameters = $connection->getParameters(); + + if (isset($parameters->alias)) { + $this->pool[$parameters->alias] = $connection; + } else { + $this->pool[] = $connection; + } + + $weight = isset($parameters->weight) ? $parameters->weight : null; + $this->distributor->add($connection, $weight); + } + + /** + * {@inheritdoc} + */ + public function remove(NodeConnectionInterface $connection) + { + if (($id = array_search($connection, $this->pool, true)) !== false) { + unset($this->pool[$id]); + $this->distributor->remove($connection); + + return true; + } + + return false; + } + + /** + * Removes a connection instance using its alias or index. + * + * @param string $connectionID Alias or index of a connection. + * + * @return bool Returns true if the connection was in the pool. + */ + public function removeById($connectionID) + { + if ($connection = $this->getConnectionById($connectionID)) { + return $this->remove($connection); + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getConnection(CommandInterface $command) + { + $slot = $this->strategy->getSlot($command); + + if (!isset($slot)) { + throw new NotSupportedException( + "Cannot use '{$command->getId()}' over clusters of connections." + ); + } + + $node = $this->distributor->getBySlot($slot); + + return $node; + } + + /** + * {@inheritdoc} + */ + public function getConnectionById($connectionID) + { + return isset($this->pool[$connectionID]) ? $this->pool[$connectionID] : null; + } + + /** + * Retrieves a connection instance from the cluster using a key. + * + * @param string $key Key string. + * + * @return NodeConnectionInterface + */ + public function getConnectionByKey($key) + { + $hash = $this->strategy->getSlotByKey($key); + $node = $this->distributor->getBySlot($hash); + + return $node; + } + + /** + * Returns the underlying command hash strategy used to hash commands by + * using keys found in their arguments. + * + * @return StrategyInterface + */ + public function getClusterStrategy() + { + return $this->strategy; + } + + /** + * {@inheritdoc} + */ + public function count() + { + return count($this->pool); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new \ArrayIterator($this->pool); + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->getConnection($command)->writeRequest($command); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->getConnection($command)->readResponse($command); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + return $this->getConnection($command)->executeCommand($command); + } + + /** + * Executes the specified Redis command on all the nodes of a cluster. + * + * @param CommandInterface $command A Redis command. + * + * @return array + */ + public function executeCommandOnNodes(CommandInterface $command) + { + $responses = array(); + + foreach ($this->pool as $connection) { + $responses[] = $connection->executeCommand($command); + } + + return $responses; + } +} diff --git a/vendor/predis/predis/src/Connection/Aggregate/RedisCluster.php b/vendor/predis/predis/src/Connection/Aggregate/RedisCluster.php new file mode 100644 index 0000000..c749cc8 --- /dev/null +++ b/vendor/predis/predis/src/Connection/Aggregate/RedisCluster.php @@ -0,0 +1,673 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection\Aggregate; + +use Predis\ClientException; +use Predis\Cluster\RedisStrategy as RedisClusterStrategy; +use Predis\Cluster\StrategyInterface; +use Predis\Command\CommandInterface; +use Predis\Command\RawCommand; +use Predis\Connection\ConnectionException; +use Predis\Connection\FactoryInterface; +use Predis\Connection\NodeConnectionInterface; +use Predis\NotSupportedException; +use Predis\Response\ErrorInterface as ErrorResponseInterface; + +/** + * Abstraction for a Redis-backed cluster of nodes (Redis >= 3.0.0). + * + * This connection backend offers smart support for redis-cluster by handling + * automatic slots map (re)generation upon -MOVED or -ASK responses returned by + * Redis when redirecting a client to a different node. + * + * The cluster can be pre-initialized using only a subset of the actual nodes in + * the cluster, Predis will do the rest by adjusting the slots map and creating + * the missing underlying connection instances on the fly. + * + * It is possible to pre-associate connections to a slots range with the "slots" + * parameter in the form "$first-$last". This can greatly reduce runtime node + * guessing and redirections. + * + * It is also possible to ask for the full and updated slots map directly to one + * of the nodes and optionally enable such a behaviour upon -MOVED redirections. + * Asking for the cluster configuration to Redis is actually done by issuing a + * CLUSTER SLOTS command to a random node in the pool. + * + * @author Daniele Alessandri + */ +class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable +{ + private $useClusterSlots = true; + private $pool = array(); + private $slots = array(); + private $slotsMap; + private $strategy; + private $connections; + private $retryLimit = 5; + + /** + * @param FactoryInterface $connections Optional connection factory. + * @param StrategyInterface $strategy Optional cluster strategy. + */ + public function __construct( + FactoryInterface $connections, + StrategyInterface $strategy = null + ) { + $this->connections = $connections; + $this->strategy = $strategy ?: new RedisClusterStrategy(); + } + + /** + * Sets the maximum number of retries for commands upon server failure. + * + * -1 = unlimited retry attempts + * 0 = no retry attempts (fails immediatly) + * n = fail only after n retry attempts + * + * @param int $retry Number of retry attempts. + */ + public function setRetryLimit($retry) + { + $this->retryLimit = (int) $retry; + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + foreach ($this->pool as $connection) { + if ($connection->isConnected()) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if ($connection = $this->getRandomConnection()) { + $connection->connect(); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + foreach ($this->pool as $connection) { + $connection->disconnect(); + } + } + + /** + * {@inheritdoc} + */ + public function add(NodeConnectionInterface $connection) + { + $this->pool[(string) $connection] = $connection; + unset($this->slotsMap); + } + + /** + * {@inheritdoc} + */ + public function remove(NodeConnectionInterface $connection) + { + if (false !== $id = array_search($connection, $this->pool, true)) { + unset( + $this->pool[$id], + $this->slotsMap + ); + + $this->slots = array_diff($this->slots, array($connection)); + + return true; + } + + return false; + } + + /** + * Removes a connection instance by using its identifier. + * + * @param string $connectionID Connection identifier. + * + * @return bool True if the connection was in the pool. + */ + public function removeById($connectionID) + { + if (isset($this->pool[$connectionID])) { + unset( + $this->pool[$connectionID], + $this->slotsMap + ); + + return true; + } + + return false; + } + + /** + * Generates the current slots map by guessing the cluster configuration out + * of the connection parameters of the connections in the pool. + * + * Generation is based on the same algorithm used by Redis to generate the + * cluster, so it is most effective when all of the connections supplied on + * initialization have the "slots" parameter properly set accordingly to the + * current cluster configuration. + * + * @return array + */ + public function buildSlotsMap() + { + $this->slotsMap = array(); + + foreach ($this->pool as $connectionID => $connection) { + $parameters = $connection->getParameters(); + + if (!isset($parameters->slots)) { + continue; + } + + foreach (explode(',', $parameters->slots) as $slotRange) { + $slots = explode('-', $slotRange, 2); + + if (!isset($slots[1])) { + $slots[1] = $slots[0]; + } + + $this->setSlots($slots[0], $slots[1], $connectionID); + } + } + + return $this->slotsMap; + } + + /** + * Queries the specified node of the cluster to fetch the updated slots map. + * + * When the connection fails, this method tries to execute the same command + * on a different connection picked at random from the pool of known nodes, + * up until the retry limit is reached. + * + * @param NodeConnectionInterface $connection Connection to a node of the cluster. + * + * @return mixed + */ + private function queryClusterNodeForSlotsMap(NodeConnectionInterface $connection) + { + $retries = 0; + $command = RawCommand::create('CLUSTER', 'SLOTS'); + + RETRY_COMMAND: { + try { + $response = $connection->executeCommand($command); + } catch (ConnectionException $exception) { + $connection = $exception->getConnection(); + $connection->disconnect(); + + $this->remove($connection); + + if ($retries === $this->retryLimit) { + throw $exception; + } + + if (!$connection = $this->getRandomConnection()) { + throw new ClientException('No connections left in the pool for `CLUSTER SLOTS`'); + } + + ++$retries; + goto RETRY_COMMAND; + } + } + + return $response; + } + + /** + * Generates an updated slots map fetching the cluster configuration using + * the CLUSTER SLOTS command against the specified node or a random one from + * the pool. + * + * @param NodeConnectionInterface $connection Optional connection instance. + * + * @return array + */ + public function askSlotsMap(NodeConnectionInterface $connection = null) + { + if (!$connection && !$connection = $this->getRandomConnection()) { + return array(); + } + + $this->resetSlotsMap(); + + $response = $this->queryClusterNodeForSlotsMap($connection); + + foreach ($response as $slots) { + // We only support master servers for now, so we ignore subsequent + // elements in the $slots array identifying slaves. + list($start, $end, $master) = $slots; + + if ($master[0] === '') { + $this->setSlots($start, $end, (string) $connection); + } else { + $this->setSlots($start, $end, "{$master[0]}:{$master[1]}"); + } + } + + return $this->slotsMap; + } + + /** + * Resets the slots map cache. + */ + public function resetSlotsMap() + { + $this->slotsMap = array(); + } + + /** + * Returns the current slots map for the cluster. + * + * The order of the returned $slot => $server dictionary is not guaranteed. + * + * @return array + */ + public function getSlotsMap() + { + if (!isset($this->slotsMap)) { + $this->slotsMap = array(); + } + + return $this->slotsMap; + } + + /** + * Pre-associates a connection to a slots range to avoid runtime guessing. + * + * @param int $first Initial slot of the range. + * @param int $last Last slot of the range. + * @param NodeConnectionInterface|string $connection ID or connection instance. + * + * @throws \OutOfBoundsException + */ + public function setSlots($first, $last, $connection) + { + if ($first < 0x0000 || $first > 0x3FFF || + $last < 0x0000 || $last > 0x3FFF || + $last < $first + ) { + throw new \OutOfBoundsException( + "Invalid slot range for $connection: [$first-$last]." + ); + } + + $slots = array_fill($first, $last - $first + 1, (string) $connection); + $this->slotsMap = $this->getSlotsMap() + $slots; + } + + /** + * Guesses the correct node associated to a given slot using a precalculated + * slots map, falling back to the same logic used by Redis to initialize a + * cluster (best-effort). + * + * @param int $slot Slot index. + * + * @return string Connection ID. + */ + protected function guessNode($slot) + { + if (!$this->pool) { + throw new ClientException('No connections available in the pool'); + } + + if (!isset($this->slotsMap)) { + $this->buildSlotsMap(); + } + + if (isset($this->slotsMap[$slot])) { + return $this->slotsMap[$slot]; + } + + $count = count($this->pool); + $index = min((int) ($slot / (int) (16384 / $count)), $count - 1); + $nodes = array_keys($this->pool); + + return $nodes[$index]; + } + + /** + * Creates a new connection instance from the given connection ID. + * + * @param string $connectionID Identifier for the connection. + * + * @return NodeConnectionInterface + */ + protected function createConnection($connectionID) + { + $separator = strrpos($connectionID, ':'); + + return $this->connections->create(array( + 'host' => substr($connectionID, 0, $separator), + 'port' => substr($connectionID, $separator + 1), + )); + } + + /** + * {@inheritdoc} + */ + public function getConnection(CommandInterface $command) + { + $slot = $this->strategy->getSlot($command); + + if (!isset($slot)) { + throw new NotSupportedException( + "Cannot use '{$command->getId()}' with redis-cluster." + ); + } + + if (isset($this->slots[$slot])) { + return $this->slots[$slot]; + } else { + return $this->getConnectionBySlot($slot); + } + } + + /** + * Returns the connection currently associated to a given slot. + * + * @param int $slot Slot index. + * + * @throws \OutOfBoundsException + * + * @return NodeConnectionInterface + */ + public function getConnectionBySlot($slot) + { + if ($slot < 0x0000 || $slot > 0x3FFF) { + throw new \OutOfBoundsException("Invalid slot [$slot]."); + } + + if (isset($this->slots[$slot])) { + return $this->slots[$slot]; + } + + $connectionID = $this->guessNode($slot); + + if (!$connection = $this->getConnectionById($connectionID)) { + $connection = $this->createConnection($connectionID); + $this->pool[$connectionID] = $connection; + } + + return $this->slots[$slot] = $connection; + } + + /** + * {@inheritdoc} + */ + public function getConnectionById($connectionID) + { + if (isset($this->pool[$connectionID])) { + return $this->pool[$connectionID]; + } + } + + /** + * Returns a random connection from the pool. + * + * @return NodeConnectionInterface|null + */ + protected function getRandomConnection() + { + if ($this->pool) { + return $this->pool[array_rand($this->pool)]; + } + } + + /** + * Permanently associates the connection instance to a new slot. + * The connection is added to the connections pool if not yet included. + * + * @param NodeConnectionInterface $connection Connection instance. + * @param int $slot Target slot index. + */ + protected function move(NodeConnectionInterface $connection, $slot) + { + $this->pool[(string) $connection] = $connection; + $this->slots[(int) $slot] = $connection; + } + + /** + * Handles -ERR responses returned by Redis. + * + * @param CommandInterface $command Command that generated the -ERR response. + * @param ErrorResponseInterface $error Redis error response object. + * + * @return mixed + */ + protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $error) + { + $details = explode(' ', $error->getMessage(), 2); + + switch ($details[0]) { + case 'MOVED': + return $this->onMovedResponse($command, $details[1]); + + case 'ASK': + return $this->onAskResponse($command, $details[1]); + + default: + return $error; + } + } + + /** + * Handles -MOVED responses by executing again the command against the node + * indicated by the Redis response. + * + * @param CommandInterface $command Command that generated the -MOVED response. + * @param string $details Parameters of the -MOVED response. + * + * @return mixed + */ + protected function onMovedResponse(CommandInterface $command, $details) + { + list($slot, $connectionID) = explode(' ', $details, 2); + + if (!$connection = $this->getConnectionById($connectionID)) { + $connection = $this->createConnection($connectionID); + } + + if ($this->useClusterSlots) { + $this->askSlotsMap($connection); + } + + $this->move($connection, $slot); + $response = $this->executeCommand($command); + + return $response; + } + + /** + * Handles -ASK responses by executing again the command against the node + * indicated by the Redis response. + * + * @param CommandInterface $command Command that generated the -ASK response. + * @param string $details Parameters of the -ASK response. + * + * @return mixed + */ + protected function onAskResponse(CommandInterface $command, $details) + { + list($slot, $connectionID) = explode(' ', $details, 2); + + if (!$connection = $this->getConnectionById($connectionID)) { + $connection = $this->createConnection($connectionID); + } + + $connection->executeCommand(RawCommand::create('ASKING')); + $response = $connection->executeCommand($command); + + return $response; + } + + /** + * Ensures that a command is executed one more time on connection failure. + * + * The connection to the node that generated the error is evicted from the + * pool before trying to fetch an updated slots map from another node. If + * the new slots map points to an unreachable server the client gives up and + * throws the exception as the nodes participating in the cluster may still + * have to agree that something changed in the configuration of the cluster. + * + * @param CommandInterface $command Command instance. + * @param string $method Actual method. + * + * @return mixed + */ + private function retryCommandOnFailure(CommandInterface $command, $method) + { + $failure = false; + + RETRY_COMMAND: { + try { + $response = $this->getConnection($command)->$method($command); + } catch (ConnectionException $exception) { + $connection = $exception->getConnection(); + $connection->disconnect(); + + $this->remove($connection); + + if ($failure) { + throw $exception; + } elseif ($this->useClusterSlots) { + $this->askSlotsMap(); + } + + $failure = true; + + goto RETRY_COMMAND; + } + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + $response = $this->retryCommandOnFailure($command, __FUNCTION__); + + if ($response instanceof ErrorResponseInterface) { + return $this->onErrorResponse($command, $response); + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function count() + { + return count($this->pool); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + if ($this->useClusterSlots) { + $slotsmap = $this->getSlotsMap() ?: $this->askSlotsMap(); + } else { + $slotsmap = $this->getSlotsMap() ?: $this->buildSlotsMap(); + } + + $connections = array(); + + foreach (array_unique($slotsmap) as $node) { + if (!$connection = $this->getConnectionById($node)) { + $this->add($connection = $this->createConnection($node)); + } + + $connections[] = $connection; + } + + return new \ArrayIterator($connections); + } + + /** + * Returns the underlying command hash strategy used to hash commands by + * using keys found in their arguments. + * + * @return StrategyInterface + */ + public function getClusterStrategy() + { + return $this->strategy; + } + + /** + * Returns the underlying connection factory used to create new connection + * instances to Redis nodes indicated by redis-cluster. + * + * @return FactoryInterface + */ + public function getConnectionFactory() + { + return $this->connections; + } + + /** + * Enables automatic fetching of the current slots map from one of the nodes + * using the CLUSTER SLOTS command. This option is enabled by default as + * asking the current slots map to Redis upon -MOVED responses may reduce + * overhead by eliminating the trial-and-error nature of the node guessing + * procedure, mostly when targeting many keys that would end up in a lot of + * redirections. + * + * The slots map can still be manually fetched using the askSlotsMap() + * method whether or not this option is enabled. + * + * @param bool $value Enable or disable the use of CLUSTER SLOTS. + */ + public function useClusterSlots($value) + { + $this->useClusterSlots = (bool) $value; + } +} diff --git a/vendor/predis/predis/src/Connection/Aggregate/ReplicationInterface.php b/vendor/predis/predis/src/Connection/Aggregate/ReplicationInterface.php new file mode 100644 index 0000000..e09e826 --- /dev/null +++ b/vendor/predis/predis/src/Connection/Aggregate/ReplicationInterface.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection\Aggregate; + +use Predis\Connection\AggregateConnectionInterface; +use Predis\Connection\NodeConnectionInterface; + +/** + * Defines a group of Redis nodes in a master / slave replication setup. + * + * @author Daniele Alessandri + */ +interface ReplicationInterface extends AggregateConnectionInterface +{ + /** + * Switches the internal connection instance in use. + * + * @param string $connection Alias of a connection + */ + public function switchTo($connection); + + /** + * Returns the connection instance currently in use by the aggregate + * connection. + * + * @return NodeConnectionInterface + */ + public function getCurrent(); + + /** + * Returns the connection instance for the master Redis node. + * + * @return NodeConnectionInterface + */ + public function getMaster(); + + /** + * Returns a list of connection instances to slave nodes. + * + * @return NodeConnectionInterface + */ + public function getSlaves(); +} diff --git a/vendor/predis/predis/src/Connection/Aggregate/SentinelReplication.php b/vendor/predis/predis/src/Connection/Aggregate/SentinelReplication.php new file mode 100644 index 0000000..5cae016 --- /dev/null +++ b/vendor/predis/predis/src/Connection/Aggregate/SentinelReplication.php @@ -0,0 +1,720 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection\Aggregate; + +use Predis\Command\CommandInterface; +use Predis\Command\RawCommand; +use Predis\CommunicationException; +use Predis\Connection\ConnectionException; +use Predis\Connection\FactoryInterface as ConnectionFactoryInterface; +use Predis\Connection\NodeConnectionInterface; +use Predis\Connection\Parameters; +use Predis\Replication\ReplicationStrategy; +use Predis\Replication\RoleException; +use Predis\Response\ErrorInterface as ErrorResponseInterface; +use Predis\Response\ServerException; + +/** + * @author Daniele Alessandri + * @author Ville Mattila + */ +class SentinelReplication implements ReplicationInterface +{ + /** + * @var NodeConnectionInterface + */ + protected $master; + + /** + * @var NodeConnectionInterface[] + */ + protected $slaves = array(); + + /** + * @var NodeConnectionInterface + */ + protected $current; + + /** + * @var string + */ + protected $service; + + /** + * @var ConnectionFactoryInterface + */ + protected $connectionFactory; + + /** + * @var ReplicationStrategy + */ + protected $strategy; + + /** + * @var NodeConnectionInterface[] + */ + protected $sentinels = array(); + + /** + * @var NodeConnectionInterface + */ + protected $sentinelConnection; + + /** + * @var float + */ + protected $sentinelTimeout = 0.100; + + /** + * Max number of automatic retries of commands upon server failure. + * + * -1 = unlimited retry attempts + * 0 = no retry attempts (fails immediatly) + * n = fail only after n retry attempts + * + * @var int + */ + protected $retryLimit = 20; + + /** + * Time to wait in milliseconds before fetching a new configuration from one + * of the sentinel servers. + * + * @var int + */ + protected $retryWait = 1000; + + /** + * Flag for automatic fetching of available sentinels. + * + * @var bool + */ + protected $updateSentinels = false; + + /** + * @param string $service Name of the service for autodiscovery. + * @param array $sentinels Sentinel servers connection parameters. + * @param ConnectionFactoryInterface $connectionFactory Connection factory instance. + * @param ReplicationStrategy $strategy Replication strategy instance. + */ + public function __construct( + $service, + array $sentinels, + ConnectionFactoryInterface $connectionFactory, + ReplicationStrategy $strategy = null + ) { + $this->sentinels = $sentinels; + $this->service = $service; + $this->connectionFactory = $connectionFactory; + $this->strategy = $strategy ?: new ReplicationStrategy(); + } + + /** + * Sets a default timeout for connections to sentinels. + * + * When "timeout" is present in the connection parameters of sentinels, its + * value overrides the default sentinel timeout. + * + * @param float $timeout Timeout value. + */ + public function setSentinelTimeout($timeout) + { + $this->sentinelTimeout = (float) $timeout; + } + + /** + * Sets the maximum number of retries for commands upon server failure. + * + * -1 = unlimited retry attempts + * 0 = no retry attempts (fails immediatly) + * n = fail only after n retry attempts + * + * @param int $retry Number of retry attempts. + */ + public function setRetryLimit($retry) + { + $this->retryLimit = (int) $retry; + } + + /** + * Sets the time to wait (in seconds) before fetching a new configuration + * from one of the sentinels. + * + * @param float $seconds Time to wait before the next attempt. + */ + public function setRetryWait($seconds) + { + $this->retryWait = (float) $seconds; + } + + /** + * Set automatic fetching of available sentinels. + * + * @param bool $update Enable or disable automatic updates. + */ + public function setUpdateSentinels($update) + { + $this->updateSentinels = (bool) $update; + } + + /** + * Resets the current connection. + */ + protected function reset() + { + $this->current = null; + } + + /** + * Wipes the current list of master and slaves nodes. + */ + protected function wipeServerList() + { + $this->reset(); + + $this->master = null; + $this->slaves = array(); + } + + /** + * {@inheritdoc} + */ + public function add(NodeConnectionInterface $connection) + { + $alias = $connection->getParameters()->alias; + + if ($alias === 'master') { + $this->master = $connection; + } else { + $this->slaves[$alias ?: count($this->slaves)] = $connection; + } + + $this->reset(); + } + + /** + * {@inheritdoc} + */ + public function remove(NodeConnectionInterface $connection) + { + if ($connection === $this->master) { + $this->master = null; + $this->reset(); + + return true; + } + + if (false !== $id = array_search($connection, $this->slaves, true)) { + unset($this->slaves[$id]); + $this->reset(); + + return true; + } + + return false; + } + + /** + * Creates a new connection to a sentinel server. + * + * @return NodeConnectionInterface + */ + protected function createSentinelConnection($parameters) + { + if ($parameters instanceof NodeConnectionInterface) { + return $parameters; + } + + if (is_string($parameters)) { + $parameters = Parameters::parse($parameters); + } + + if (is_array($parameters)) { + // We explicitly set "database" and "password" to null, + // so that no AUTH and SELECT command is send to the sentinels. + $parameters['database'] = null; + $parameters['password'] = null; + + if (!isset($parameters['timeout'])) { + $parameters['timeout'] = $this->sentinelTimeout; + } + } + + $connection = $this->connectionFactory->create($parameters); + + return $connection; + } + + /** + * Returns the current sentinel connection. + * + * If there is no active sentinel connection, a new connection is created. + * + * @return NodeConnectionInterface + */ + public function getSentinelConnection() + { + if (!$this->sentinelConnection) { + if (!$this->sentinels) { + throw new \Predis\ClientException('No sentinel server available for autodiscovery.'); + } + + $sentinel = array_shift($this->sentinels); + $this->sentinelConnection = $this->createSentinelConnection($sentinel); + } + + return $this->sentinelConnection; + } + + /** + * Fetches an updated list of sentinels from a sentinel. + */ + public function updateSentinels() + { + SENTINEL_QUERY: { + $sentinel = $this->getSentinelConnection(); + + try { + $payload = $sentinel->executeCommand( + RawCommand::create('SENTINEL', 'sentinels', $this->service) + ); + + $this->sentinels = array(); + // NOTE: sentinel server does not return itself, so we add it back. + $this->sentinels[] = $sentinel->getParameters()->toArray(); + + foreach ($payload as $sentinel) { + $this->sentinels[] = array( + 'host' => $sentinel[3], + 'port' => $sentinel[5], + ); + } + } catch (ConnectionException $exception) { + $this->sentinelConnection = null; + + goto SENTINEL_QUERY; + } + } + } + + /** + * Fetches the details for the master and slave servers from a sentinel. + */ + public function querySentinel() + { + $this->wipeServerList(); + + $this->updateSentinels(); + $this->getMaster(); + $this->getSlaves(); + } + + /** + * Handles error responses returned by redis-sentinel. + * + * @param NodeConnectionInterface $sentinel Connection to a sentinel server. + * @param ErrorResponseInterface $error Error response. + */ + private function handleSentinelErrorResponse(NodeConnectionInterface $sentinel, ErrorResponseInterface $error) + { + if ($error->getErrorType() === 'IDONTKNOW') { + throw new ConnectionException($sentinel, $error->getMessage()); + } else { + throw new ServerException($error->getMessage()); + } + } + + /** + * Fetches the details for the master server from a sentinel. + * + * @param NodeConnectionInterface $sentinel Connection to a sentinel server. + * @param string $service Name of the service. + * + * @return array + */ + protected function querySentinelForMaster(NodeConnectionInterface $sentinel, $service) + { + $payload = $sentinel->executeCommand( + RawCommand::create('SENTINEL', 'get-master-addr-by-name', $service) + ); + + if ($payload === null) { + throw new ServerException('ERR No such master with that name'); + } + + if ($payload instanceof ErrorResponseInterface) { + $this->handleSentinelErrorResponse($sentinel, $payload); + } + + return array( + 'host' => $payload[0], + 'port' => $payload[1], + 'alias' => 'master', + ); + } + + /** + * Fetches the details for the slave servers from a sentinel. + * + * @param NodeConnectionInterface $sentinel Connection to a sentinel server. + * @param string $service Name of the service. + * + * @return array + */ + protected function querySentinelForSlaves(NodeConnectionInterface $sentinel, $service) + { + $slaves = array(); + + $payload = $sentinel->executeCommand( + RawCommand::create('SENTINEL', 'slaves', $service) + ); + + if ($payload instanceof ErrorResponseInterface) { + $this->handleSentinelErrorResponse($sentinel, $payload); + } + + foreach ($payload as $slave) { + $flags = explode(',', $slave[9]); + + if (array_intersect($flags, array('s_down', 'o_down', 'disconnected'))) { + continue; + } + + $slaves[] = array( + 'host' => $slave[3], + 'port' => $slave[5], + 'alias' => "slave-$slave[1]", + ); + } + + return $slaves; + } + + /** + * {@inheritdoc} + */ + public function getCurrent() + { + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function getMaster() + { + if ($this->master) { + return $this->master; + } + + if ($this->updateSentinels) { + $this->updateSentinels(); + } + + SENTINEL_QUERY: { + $sentinel = $this->getSentinelConnection(); + + try { + $masterParameters = $this->querySentinelForMaster($sentinel, $this->service); + $masterConnection = $this->connectionFactory->create($masterParameters); + + $this->add($masterConnection); + } catch (ConnectionException $exception) { + $this->sentinelConnection = null; + + goto SENTINEL_QUERY; + } + } + + return $masterConnection; + } + + /** + * {@inheritdoc} + */ + public function getSlaves() + { + if ($this->slaves) { + return array_values($this->slaves); + } + + if ($this->updateSentinels) { + $this->updateSentinels(); + } + + SENTINEL_QUERY: { + $sentinel = $this->getSentinelConnection(); + + try { + $slavesParameters = $this->querySentinelForSlaves($sentinel, $this->service); + + foreach ($slavesParameters as $slaveParameters) { + $this->add($this->connectionFactory->create($slaveParameters)); + } + } catch (ConnectionException $exception) { + $this->sentinelConnection = null; + + goto SENTINEL_QUERY; + } + } + + return array_values($this->slaves ?: array()); + } + + /** + * Returns a random slave. + * + * @return NodeConnectionInterface + */ + protected function pickSlave() + { + if ($slaves = $this->getSlaves()) { + return $slaves[rand(1, count($slaves)) - 1]; + } + } + + /** + * Returns the connection instance in charge for the given command. + * + * @param CommandInterface $command Command instance. + * + * @return NodeConnectionInterface + */ + private function getConnectionInternal(CommandInterface $command) + { + if (!$this->current) { + if ($this->strategy->isReadOperation($command) && $slave = $this->pickSlave()) { + $this->current = $slave; + } else { + $this->current = $this->getMaster(); + } + + return $this->current; + } + + if ($this->current === $this->master) { + return $this->current; + } + + if (!$this->strategy->isReadOperation($command)) { + $this->current = $this->getMaster(); + } + + return $this->current; + } + + /** + * Asserts that the specified connection matches an expected role. + * + * @param NodeConnectionInterface $sentinel Connection to a redis server. + * @param string $role Expected role of the server ("master", "slave" or "sentinel"). + */ + protected function assertConnectionRole(NodeConnectionInterface $connection, $role) + { + $role = strtolower($role); + $actualRole = $connection->executeCommand(RawCommand::create('ROLE')); + + if ($role !== $actualRole[0]) { + throw new RoleException($connection, "Expected $role but got $actualRole[0] [$connection]"); + } + } + + /** + * {@inheritdoc} + */ + public function getConnection(CommandInterface $command) + { + $connection = $this->getConnectionInternal($command); + + if (!$connection->isConnected()) { + // When we do not have any available slave in the pool we can expect + // read-only operations to hit the master server. + $expectedRole = $this->strategy->isReadOperation($command) && $this->slaves ? 'slave' : 'master'; + $this->assertConnectionRole($connection, $expectedRole); + } + + return $connection; + } + + /** + * {@inheritdoc} + */ + public function getConnectionById($connectionId) + { + if ($connectionId === 'master') { + return $this->getMaster(); + } + + $this->getSlaves(); + + if (isset($this->slaves[$connectionId])) { + return $this->slaves[$connectionId]; + } + } + + /** + * {@inheritdoc} + */ + public function switchTo($connection) + { + if (!$connection instanceof NodeConnectionInterface) { + $connection = $this->getConnectionById($connection); + } + + if ($connection && $connection === $this->current) { + return; + } + + if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) { + throw new \InvalidArgumentException('Invalid connection or connection not found.'); + } + + $connection->connect(); + + if ($this->current) { + $this->current->disconnect(); + } + + $this->current = $connection; + } + + /** + * Switches to the master server. + */ + public function switchToMaster() + { + $this->switchTo('master'); + } + + /** + * Switches to a random slave server. + */ + public function switchToSlave() + { + $connection = $this->pickSlave(); + $this->switchTo($connection); + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + return $this->current ? $this->current->isConnected() : false; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (!$this->current) { + if (!$this->current = $this->pickSlave()) { + $this->current = $this->getMaster(); + } + } + + $this->current->connect(); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + if ($this->master) { + $this->master->disconnect(); + } + + foreach ($this->slaves as $connection) { + $connection->disconnect(); + } + } + + /** + * Retries the execution of a command upon server failure after asking a new + * configuration to one of the sentinels. + * + * @param CommandInterface $command Command instance. + * @param string $method Actual method. + * + * @return mixed + */ + private function retryCommandOnFailure(CommandInterface $command, $method) + { + $retries = 0; + + SENTINEL_RETRY: { + try { + $response = $this->getConnection($command)->$method($command); + } catch (CommunicationException $exception) { + $this->wipeServerList(); + $exception->getConnection()->disconnect(); + + if ($retries == $this->retryLimit) { + throw $exception; + } + + usleep($this->retryWait * 1000); + + ++$retries; + goto SENTINEL_RETRY; + } + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + return $this->retryCommandOnFailure($command, __FUNCTION__); + } + + /** + * Returns the underlying replication strategy. + * + * @return ReplicationStrategy + */ + public function getReplicationStrategy() + { + return $this->strategy; + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array( + 'master', 'slaves', 'service', 'sentinels', 'connectionFactory', 'strategy', + ); + } +} diff --git a/vendor/predis/predis/src/Connection/AggregateConnectionInterface.php b/vendor/predis/predis/src/Connection/AggregateConnectionInterface.php new file mode 100644 index 0000000..7eeaede --- /dev/null +++ b/vendor/predis/predis/src/Connection/AggregateConnectionInterface.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 Predis\Connection; + +use Predis\Command\CommandInterface; + +/** + * Defines a virtual connection composed of multiple connection instances to + * single Redis nodes. + * + * @author Daniele Alessandri + */ +interface AggregateConnectionInterface extends ConnectionInterface +{ + /** + * Adds a connection instance to the aggregate connection. + * + * @param NodeConnectionInterface $connection Connection instance. + */ + public function add(NodeConnectionInterface $connection); + + /** + * Removes the specified connection instance from the aggregate connection. + * + * @param NodeConnectionInterface $connection Connection instance. + * + * @return bool Returns true if the connection was in the pool. + */ + public function remove(NodeConnectionInterface $connection); + + /** + * Returns the connection instance in charge for the given command. + * + * @param CommandInterface $command Command instance. + * + * @return NodeConnectionInterface + */ + public function getConnection(CommandInterface $command); + + /** + * Returns a connection instance from the aggregate connection by its alias. + * + * @param string $connectionID Connection alias. + * + * @return NodeConnectionInterface|null + */ + public function getConnectionById($connectionID); +} diff --git a/vendor/predis/predis/src/Connection/CompositeConnectionInterface.php b/vendor/predis/predis/src/Connection/CompositeConnectionInterface.php new file mode 100644 index 0000000..286e082 --- /dev/null +++ b/vendor/predis/predis/src/Connection/CompositeConnectionInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +/** + * Defines a connection to communicate with a single Redis server that leverages + * an external protocol processor to handle pluggable protocol handlers. + * + * @author Daniele Alessandri + */ +interface CompositeConnectionInterface extends NodeConnectionInterface +{ + /** + * Returns the protocol processor used by the connection. + */ + public function getProtocol(); + + /** + * Writes the buffer containing over the connection. + * + * @param string $buffer String buffer to be sent over the connection. + */ + public function writeBuffer($buffer); + + /** + * Reads the given number of bytes from the connection. + * + * @param int $length Number of bytes to read from the connection. + * + * @return string + */ + public function readBuffer($length); + + /** + * Reads a line from the connection. + * + * @param string + */ + public function readLine(); +} diff --git a/vendor/predis/predis/src/Connection/CompositeStreamConnection.php b/vendor/predis/predis/src/Connection/CompositeStreamConnection.php new file mode 100644 index 0000000..7a35340 --- /dev/null +++ b/vendor/predis/predis/src/Connection/CompositeStreamConnection.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; +use Predis\Protocol\ProtocolProcessorInterface; +use Predis\Protocol\Text\ProtocolProcessor as TextProtocolProcessor; + +/** + * Connection abstraction to Redis servers based on PHP's stream that uses an + * external protocol processor defining the protocol used for the communication. + * + * @author Daniele Alessandri + */ +class CompositeStreamConnection extends StreamConnection implements CompositeConnectionInterface +{ + protected $protocol; + + /** + * @param ParametersInterface $parameters Initialization parameters for the connection. + * @param ProtocolProcessorInterface $protocol Protocol processor. + */ + public function __construct( + ParametersInterface $parameters, + ProtocolProcessorInterface $protocol = null + ) { + $this->parameters = $this->assertParameters($parameters); + $this->protocol = $protocol ?: new TextProtocolProcessor(); + } + + /** + * {@inheritdoc} + */ + public function getProtocol() + { + return $this->protocol; + } + + /** + * {@inheritdoc} + */ + public function writeBuffer($buffer) + { + $this->write($buffer); + } + + /** + * {@inheritdoc} + */ + public function readBuffer($length) + { + if ($length <= 0) { + throw new \InvalidArgumentException('Length parameter must be greater than 0.'); + } + + $value = ''; + $socket = $this->getResource(); + + do { + $chunk = fread($socket, $length); + + if ($chunk === false || $chunk === '') { + $this->onConnectionError('Error while reading bytes from the server.'); + } + + $value .= $chunk; + } while (($length -= strlen($chunk)) > 0); + + return $value; + } + + /** + * {@inheritdoc} + */ + public function readLine() + { + $value = ''; + $socket = $this->getResource(); + + do { + $chunk = fgets($socket); + + if ($chunk === false || $chunk === '') { + $this->onConnectionError('Error while reading line from the server.'); + } + + $value .= $chunk; + } while (substr($value, -2) !== "\r\n"); + + return substr($value, 0, -2); + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->protocol->write($this, $command); + } + + /** + * {@inheritdoc} + */ + public function read() + { + return $this->protocol->read($this); + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array_merge(parent::__sleep(), array('protocol')); + } +} diff --git a/vendor/predis/predis/src/Connection/ConnectionException.php b/vendor/predis/predis/src/Connection/ConnectionException.php new file mode 100644 index 0000000..ef2e9d7 --- /dev/null +++ b/vendor/predis/predis/src/Connection/ConnectionException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\CommunicationException; + +/** + * Exception class that identifies connection-related errors. + * + * @author Daniele Alessandri + */ +class ConnectionException extends CommunicationException +{ +} diff --git a/vendor/predis/predis/src/Connection/ConnectionInterface.php b/vendor/predis/predis/src/Connection/ConnectionInterface.php new file mode 100644 index 0000000..11ace1b --- /dev/null +++ b/vendor/predis/predis/src/Connection/ConnectionInterface.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; + +/** + * Defines a connection object used to communicate with one or multiple + * Redis servers. + * + * @author Daniele Alessandri + */ +interface ConnectionInterface +{ + /** + * Opens the connection to Redis. + */ + public function connect(); + + /** + * Closes the connection to Redis. + */ + public function disconnect(); + + /** + * Checks if the connection to Redis is considered open. + * + * @return bool + */ + public function isConnected(); + + /** + * Writes the request for the given command over the connection. + * + * @param CommandInterface $command Command instance. + */ + public function writeRequest(CommandInterface $command); + + /** + * Reads the response to the given command from the connection. + * + * @param CommandInterface $command Command instance. + * + * @return mixed + */ + public function readResponse(CommandInterface $command); + + /** + * Writes a request for the given command over the connection and reads back + * the response returned by Redis. + * + * @param CommandInterface $command Command instance. + * + * @return mixed + */ + public function executeCommand(CommandInterface $command); +} diff --git a/vendor/predis/predis/src/Connection/Factory.php b/vendor/predis/predis/src/Connection/Factory.php new file mode 100644 index 0000000..9c7272d --- /dev/null +++ b/vendor/predis/predis/src/Connection/Factory.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\RawCommand; + +/** + * Standard connection factory for creating connections to Redis nodes. + * + * @author Daniele Alessandri + */ +class Factory implements FactoryInterface +{ + private $defaults = array(); + + protected $schemes = array( + 'tcp' => 'Predis\Connection\StreamConnection', + 'unix' => 'Predis\Connection\StreamConnection', + 'tls' => 'Predis\Connection\StreamConnection', + 'redis' => 'Predis\Connection\StreamConnection', + 'rediss' => 'Predis\Connection\StreamConnection', + 'http' => 'Predis\Connection\WebdisConnection', + ); + + /** + * Checks if the provided argument represents a valid connection class + * implementing Predis\Connection\NodeConnectionInterface. Optionally, + * callable objects are used for lazy initialization of connection objects. + * + * @param mixed $initializer FQN of a connection class or a callable for lazy initialization. + * + * @throws \InvalidArgumentException + * + * @return mixed + */ + protected function checkInitializer($initializer) + { + if (is_callable($initializer)) { + return $initializer; + } + + $class = new \ReflectionClass($initializer); + + if (!$class->isSubclassOf('Predis\Connection\NodeConnectionInterface')) { + throw new \InvalidArgumentException( + 'A connection initializer must be a valid connection class or a callable object.' + ); + } + + return $initializer; + } + + /** + * {@inheritdoc} + */ + public function define($scheme, $initializer) + { + $this->schemes[$scheme] = $this->checkInitializer($initializer); + } + + /** + * {@inheritdoc} + */ + public function undefine($scheme) + { + unset($this->schemes[$scheme]); + } + + /** + * {@inheritdoc} + */ + public function create($parameters) + { + if (!$parameters instanceof ParametersInterface) { + $parameters = $this->createParameters($parameters); + } + + $scheme = $parameters->scheme; + + if (!isset($this->schemes[$scheme])) { + throw new \InvalidArgumentException("Unknown connection scheme: '$scheme'."); + } + + $initializer = $this->schemes[$scheme]; + + if (is_callable($initializer)) { + $connection = call_user_func($initializer, $parameters, $this); + } else { + $connection = new $initializer($parameters); + $this->prepareConnection($connection); + } + + if (!$connection instanceof NodeConnectionInterface) { + throw new \UnexpectedValueException( + 'Objects returned by connection initializers must implement '. + "'Predis\Connection\NodeConnectionInterface'." + ); + } + + return $connection; + } + + /** + * {@inheritdoc} + */ + public function aggregate(AggregateConnectionInterface $connection, array $parameters) + { + foreach ($parameters as $node) { + $connection->add($node instanceof NodeConnectionInterface ? $node : $this->create($node)); + } + } + + /** + * Assigns a default set of parameters applied to new connections. + * + * The set of parameters passed to create a new connection have precedence + * over the default values set for the connection factory. + * + * @param array $parameters Set of connection parameters. + */ + public function setDefaultParameters(array $parameters) + { + $this->defaults = $parameters; + } + + /** + * Returns the default set of parameters applied to new connections. + * + * @return array + */ + public function getDefaultParameters() + { + return $this->defaults; + } + + /** + * Creates a connection parameters instance from the supplied argument. + * + * @param mixed $parameters Original connection parameters. + * + * @return ParametersInterface + */ + protected function createParameters($parameters) + { + if (is_string($parameters)) { + $parameters = Parameters::parse($parameters); + } else { + $parameters = $parameters ?: array(); + } + + if ($this->defaults) { + $parameters += $this->defaults; + } + + return new Parameters($parameters); + } + + /** + * Prepares a connection instance after its initialization. + * + * @param NodeConnectionInterface $connection Connection instance. + */ + protected function prepareConnection(NodeConnectionInterface $connection) + { + $parameters = $connection->getParameters(); + + if (isset($parameters->password)) { + $connection->addConnectCommand( + new RawCommand(array('AUTH', $parameters->password)) + ); + } + + if (isset($parameters->database)) { + $connection->addConnectCommand( + new RawCommand(array('SELECT', $parameters->database)) + ); + } + } +} diff --git a/vendor/predis/predis/src/Connection/FactoryInterface.php b/vendor/predis/predis/src/Connection/FactoryInterface.php new file mode 100644 index 0000000..2bae083 --- /dev/null +++ b/vendor/predis/predis/src/Connection/FactoryInterface.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +/** + * Interface for classes providing a factory of connections to Redis nodes. + * + * @author Daniele Alessandri + */ +interface FactoryInterface +{ + /** + * Defines or overrides the connection class identified by a scheme prefix. + * + * @param string $scheme Target connection scheme. + * @param mixed $initializer Fully-qualified name of a class or a callable for lazy initialization. + */ + public function define($scheme, $initializer); + + /** + * Undefines the connection identified by a scheme prefix. + * + * @param string $scheme Target connection scheme. + */ + public function undefine($scheme); + + /** + * Creates a new connection object. + * + * @param mixed $parameters Initialization parameters for the connection. + * + * @return NodeConnectionInterface + */ + public function create($parameters); + + /** + * Aggregates single connections into an aggregate connection instance. + * + * @param AggregateConnectionInterface $aggregate Aggregate connection instance. + * @param array $parameters List of parameters for each connection. + */ + public function aggregate(AggregateConnectionInterface $aggregate, array $parameters); +} diff --git a/vendor/predis/predis/src/Connection/NodeConnectionInterface.php b/vendor/predis/predis/src/Connection/NodeConnectionInterface.php new file mode 100644 index 0000000..665b862 --- /dev/null +++ b/vendor/predis/predis/src/Connection/NodeConnectionInterface.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; + +/** + * Defines a connection used to communicate with a single Redis node. + * + * @author Daniele Alessandri + */ +interface NodeConnectionInterface extends ConnectionInterface +{ + /** + * Returns a string representation of the connection. + * + * @return string + */ + public function __toString(); + + /** + * Returns the underlying resource used to communicate with Redis. + * + * @return mixed + */ + public function getResource(); + + /** + * Returns the parameters used to initialize the connection. + * + * @return ParametersInterface + */ + public function getParameters(); + + /** + * Pushes the given command into a queue of commands executed when + * establishing the actual connection to Redis. + * + * @param CommandInterface $command Instance of a Redis command. + */ + public function addConnectCommand(CommandInterface $command); + + /** + * Reads a response from the server. + * + * @return mixed + */ + public function read(); +} diff --git a/vendor/predis/predis/src/Connection/Parameters.php b/vendor/predis/predis/src/Connection/Parameters.php new file mode 100644 index 0000000..3349c96 --- /dev/null +++ b/vendor/predis/predis/src/Connection/Parameters.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +/** + * Container for connection parameters used to initialize connections to Redis. + * + * {@inheritdoc} + * + * @author Daniele Alessandri + */ +class Parameters implements ParametersInterface +{ + private $parameters; + + private static $defaults = array( + 'scheme' => 'tcp', + 'host' => '127.0.0.1', + 'port' => 6379, + ); + + /** + * @param array $parameters Named array of connection parameters. + */ + public function __construct(array $parameters = array()) + { + $this->parameters = $this->filter($parameters) + $this->getDefaults(); + } + + /** + * Returns some default parameters with their values. + * + * @return array + */ + protected function getDefaults() + { + return self::$defaults; + } + + /** + * Creates a new instance by supplying the initial parameters either in the + * form of an URI string or a named array. + * + * @param array|string $parameters Set of connection parameters. + * + * @return Parameters + */ + public static function create($parameters) + { + if (is_string($parameters)) { + $parameters = static::parse($parameters); + } + + return new static($parameters ?: array()); + } + + /** + * Parses an URI string returning an array of connection parameters. + * + * When using the "redis" and "rediss" schemes the URI is parsed according + * to the rules defined by the provisional registration documents approved + * by IANA. If the URI has a password in its "user-information" part or a + * database number in the "path" part these values override the values of + * "password" and "database" if they are present in the "query" part. + * + * @link http://www.iana.org/assignments/uri-schemes/prov/redis + * @link http://www.iana.org/assignments/uri-schemes/prov/rediss + * + * @param string $uri URI string. + * + * @throws \InvalidArgumentException + * + * @return array + */ + public static function parse($uri) + { + if (stripos($uri, 'unix://') === 0) { + // parse_url() can parse unix:/path/to/sock so we do not need the + // unix:///path/to/sock hack, we will support it anyway until 2.0. + $uri = str_ireplace('unix://', 'unix:', $uri); + } + + if (!$parsed = parse_url($uri)) { + throw new \InvalidArgumentException("Invalid parameters URI: $uri"); + } + + if ( + isset($parsed['host']) + && false !== strpos($parsed['host'], '[') + && false !== strpos($parsed['host'], ']') + ) { + $parsed['host'] = substr($parsed['host'], 1, -1); + } + + if (isset($parsed['query'])) { + parse_str($parsed['query'], $queryarray); + unset($parsed['query']); + + $parsed = array_merge($parsed, $queryarray); + } + + if (stripos($uri, 'redis') === 0) { + if (isset($parsed['pass'])) { + $parsed['password'] = $parsed['pass']; + unset($parsed['pass']); + } + + if (isset($parsed['path']) && preg_match('/^\/(\d+)(\/.*)?/', $parsed['path'], $path)) { + $parsed['database'] = $path[1]; + + if (isset($path[2])) { + $parsed['path'] = $path[2]; + } else { + unset($parsed['path']); + } + } + } + + return $parsed; + } + + /** + * Validates and converts each value of the connection parameters array. + * + * @param array $parameters Connection parameters. + * + * @return array + */ + protected function filter(array $parameters) + { + return $parameters ?: array(); + } + + /** + * {@inheritdoc} + */ + public function __get($parameter) + { + if (isset($this->parameters[$parameter])) { + return $this->parameters[$parameter]; + } + } + + /** + * {@inheritdoc} + */ + public function __isset($parameter) + { + return isset($this->parameters[$parameter]); + } + + /** + * {@inheritdoc} + */ + public function toArray() + { + return $this->parameters; + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array('parameters'); + } +} diff --git a/vendor/predis/predis/src/Connection/ParametersInterface.php b/vendor/predis/predis/src/Connection/ParametersInterface.php new file mode 100644 index 0000000..fd8a908 --- /dev/null +++ b/vendor/predis/predis/src/Connection/ParametersInterface.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 Predis\Connection; + +/** + * Interface defining a container for connection parameters. + * + * The actual list of connection parameters depends on the features supported by + * each connection backend class (please refer to their specific documentation), + * but the most common parameters used through the library are: + * + * @property-read string scheme Connection scheme, such as 'tcp' or 'unix'. + * @property-read string host IP address or hostname of Redis. + * @property-read int port TCP port on which Redis is listening to. + * @property-read string path Path of a UNIX domain socket file. + * @property-read string alias Alias for the connection. + * @property-read float timeout Timeout for the connect() operation. + * @property-read float read_write_timeout Timeout for read() and write() operations. + * @property-read bool async_connect Performs the connect() operation asynchronously. + * @property-read bool tcp_nodelay Toggles the Nagle's algorithm for coalescing. + * @property-read bool persistent Leaves the connection open after a GC collection. + * @property-read string password Password to access Redis (see the AUTH command). + * @property-read string database Database index (see the SELECT command). + * + * @author Daniele Alessandri + */ +interface ParametersInterface +{ + /** + * Checks if the specified parameters is set. + * + * @param string $parameter Name of the parameter. + * + * @return bool + */ + public function __isset($parameter); + + /** + * Returns the value of the specified parameter. + * + * @param string $parameter Name of the parameter. + * + * @return mixed|null + */ + public function __get($parameter); + + /** + * Returns an array representation of the connection parameters. + * + * @return array + */ + public function toArray(); +} diff --git a/vendor/predis/predis/src/Connection/PhpiredisSocketConnection.php b/vendor/predis/predis/src/Connection/PhpiredisSocketConnection.php new file mode 100644 index 0000000..edded2d --- /dev/null +++ b/vendor/predis/predis/src/Connection/PhpiredisSocketConnection.php @@ -0,0 +1,418 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; +use Predis\NotSupportedException; +use Predis\Response\Error as ErrorResponse; +use Predis\Response\ErrorInterface as ErrorResponseInterface; +use Predis\Response\Status as StatusResponse; + +/** + * This class provides the implementation of a Predis connection that uses the + * PHP socket extension for network communication and wraps the phpiredis C + * extension (PHP bindings for hiredis) to parse the Redis protocol. + * + * This class is intended to provide an optional low-overhead alternative for + * processing responses from Redis compared to the standard pure-PHP classes. + * Differences in speed when dealing with short inline responses are practically + * nonexistent, the actual speed boost is for big multibulk responses when this + * protocol processor can parse and return responses very fast. + * + * For instructions on how to build and install the phpiredis extension, please + * consult the repository of the project. + * + * The connection parameters supported by this class are: + * + * - scheme: it can be either 'redis', 'tcp' or 'unix'. + * - host: hostname or IP address of the server. + * - port: TCP port of the server. + * - path: path of a UNIX domain socket when scheme is 'unix'. + * - timeout: timeout to perform the connection (default is 5 seconds). + * - read_write_timeout: timeout of read / write operations. + * + * @link http://github.com/nrk/phpiredis + * + * @author Daniele Alessandri + */ +class PhpiredisSocketConnection extends AbstractConnection +{ + private $reader; + + /** + * {@inheritdoc} + */ + public function __construct(ParametersInterface $parameters) + { + $this->assertExtensions(); + + parent::__construct($parameters); + + $this->reader = $this->createReader(); + } + + /** + * Disconnects from the server and destroys the underlying resource and the + * protocol reader resource when PHP's garbage collector kicks in. + */ + public function __destruct() + { + phpiredis_reader_destroy($this->reader); + + parent::__destruct(); + } + + /** + * Checks if the socket and phpiredis extensions are loaded in PHP. + */ + protected function assertExtensions() + { + if (!extension_loaded('sockets')) { + throw new NotSupportedException( + 'The "sockets" extension is required by this connection backend.' + ); + } + + if (!extension_loaded('phpiredis')) { + throw new NotSupportedException( + 'The "phpiredis" extension is required by this connection backend.' + ); + } + } + + /** + * {@inheritdoc} + */ + protected function assertParameters(ParametersInterface $parameters) + { + switch ($parameters->scheme) { + case 'tcp': + case 'redis': + case 'unix': + break; + + default: + throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'."); + } + + if (isset($parameters->persistent)) { + throw new NotSupportedException( + 'Persistent connections are not supported by this connection backend.' + ); + } + + return $parameters; + } + + /** + * Creates a new instance of the protocol reader resource. + * + * @return resource + */ + private function createReader() + { + $reader = phpiredis_reader_create(); + + phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); + phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); + + return $reader; + } + + /** + * Returns the underlying protocol reader resource. + * + * @return resource + */ + protected function getReader() + { + return $this->reader; + } + + /** + * Returns the handler used by the protocol reader for inline responses. + * + * @return \Closure + */ + protected function getStatusHandler() + { + static $statusHandler; + + if (!$statusHandler) { + $statusHandler = function ($payload) { + return StatusResponse::get($payload); + }; + } + + return $statusHandler; + } + + /** + * Returns the handler used by the protocol reader for error responses. + * + * @return \Closure + */ + protected function getErrorHandler() + { + static $errorHandler; + + if (!$errorHandler) { + $errorHandler = function ($errorMessage) { + return new ErrorResponse($errorMessage); + }; + } + + return $errorHandler; + } + + /** + * Helper method used to throw exceptions on socket errors. + */ + private function emitSocketError() + { + $errno = socket_last_error(); + $errstr = socket_strerror($errno); + + $this->disconnect(); + + $this->onConnectionError(trim($errstr), $errno); + } + + /** + * Gets the address of an host from connection parameters. + * + * @param ParametersInterface $parameters Parameters used to initialize the connection. + * + * @return string + */ + protected static function getAddress(ParametersInterface $parameters) + { + if (filter_var($host = $parameters->host, FILTER_VALIDATE_IP)) { + return $host; + } + + if ($host === $address = gethostbyname($host)) { + return false; + } + + return $address; + } + + /** + * {@inheritdoc} + */ + protected function createResource() + { + $parameters = $this->parameters; + + if ($parameters->scheme === 'unix') { + $address = $parameters->path; + $domain = AF_UNIX; + $protocol = 0; + } else { + if (false === $address = self::getAddress($parameters)) { + $this->onConnectionError("Cannot resolve the address of '$parameters->host'."); + } + + $domain = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? AF_INET6 : AF_INET; + $protocol = SOL_TCP; + } + + $socket = @socket_create($domain, SOCK_STREAM, $protocol); + + if (!is_resource($socket)) { + $this->emitSocketError(); + } + + $this->setSocketOptions($socket, $parameters); + $this->connectWithTimeout($socket, $address, $parameters); + + return $socket; + } + + /** + * Sets options on the socket resource from the connection parameters. + * + * @param resource $socket Socket resource. + * @param ParametersInterface $parameters Parameters used to initialize the connection. + */ + private function setSocketOptions($socket, ParametersInterface $parameters) + { + if ($parameters->scheme !== 'unix') { + if (!socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1)) { + $this->emitSocketError(); + } + + if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) { + $this->emitSocketError(); + } + } + + if (isset($parameters->read_write_timeout)) { + $rwtimeout = (float) $parameters->read_write_timeout; + $timeoutSec = floor($rwtimeout); + $timeoutUsec = ($rwtimeout - $timeoutSec) * 1000000; + + $timeout = array( + 'sec' => $timeoutSec, + 'usec' => $timeoutUsec, + ); + + if (!socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout)) { + $this->emitSocketError(); + } + + if (!socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout)) { + $this->emitSocketError(); + } + } + } + + /** + * Opens the actual connection to the server with a timeout. + * + * @param resource $socket Socket resource. + * @param string $address IP address (DNS-resolved from hostname) + * @param ParametersInterface $parameters Parameters used to initialize the connection. + * + * @return string + */ + private function connectWithTimeout($socket, $address, ParametersInterface $parameters) + { + socket_set_nonblock($socket); + + if (@socket_connect($socket, $address, (int) $parameters->port) === false) { + $error = socket_last_error(); + + if ($error != SOCKET_EINPROGRESS && $error != SOCKET_EALREADY) { + $this->emitSocketError(); + } + } + + socket_set_block($socket); + + $null = null; + $selectable = array($socket); + + $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0); + $timeoutSecs = floor($timeout); + $timeoutUSecs = ($timeout - $timeoutSecs) * 1000000; + + $selected = socket_select($selectable, $selectable, $null, $timeoutSecs, $timeoutUSecs); + + if ($selected === 2) { + $this->onConnectionError('Connection refused.', SOCKET_ECONNREFUSED); + } + + if ($selected === 0) { + $this->onConnectionError('Connection timed out.', SOCKET_ETIMEDOUT); + } + + if ($selected === false) { + $this->emitSocketError(); + } + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (parent::connect() && $this->initCommands) { + foreach ($this->initCommands as $command) { + $response = $this->executeCommand($command); + + if ($response instanceof ErrorResponseInterface) { + $this->onConnectionError("`{$command->getId()}` failed: $response", 0); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + if ($this->isConnected()) { + socket_close($this->getResource()); + parent::disconnect(); + } + } + + /** + * {@inheritdoc} + */ + protected function write($buffer) + { + $socket = $this->getResource(); + + while (($length = strlen($buffer)) > 0) { + $written = socket_write($socket, $buffer, $length); + + if ($length === $written) { + return; + } + + if ($written === false) { + $this->onConnectionError('Error while writing bytes to the server.'); + } + + $buffer = substr($buffer, $written); + } + } + + /** + * {@inheritdoc} + */ + public function read() + { + $socket = $this->getResource(); + $reader = $this->reader; + + while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) { + if (@socket_recv($socket, $buffer, 4096, 0) === false || $buffer === '' || $buffer === null) { + $this->emitSocketError(); + } + + phpiredis_reader_feed($reader, $buffer); + } + + if ($state === PHPIREDIS_READER_STATE_COMPLETE) { + return phpiredis_reader_get_reply($reader); + } else { + $this->onProtocolError(phpiredis_reader_get_error($reader)); + + return; + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $arguments = $command->getArguments(); + array_unshift($arguments, $command->getId()); + + $this->write(phpiredis_format_command($arguments)); + } + + /** + * {@inheritdoc} + */ + public function __wakeup() + { + $this->assertExtensions(); + $this->reader = $this->createReader(); + } +} diff --git a/vendor/predis/predis/src/Connection/PhpiredisStreamConnection.php b/vendor/predis/predis/src/Connection/PhpiredisStreamConnection.php new file mode 100644 index 0000000..f0b719b --- /dev/null +++ b/vendor/predis/predis/src/Connection/PhpiredisStreamConnection.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; +use Predis\NotSupportedException; +use Predis\Response\Error as ErrorResponse; +use Predis\Response\Status as StatusResponse; + +/** + * This class provides the implementation of a Predis connection that uses PHP's + * streams for network communication and wraps the phpiredis C extension (PHP + * bindings for hiredis) to parse and serialize the Redis protocol. + * + * This class is intended to provide an optional low-overhead alternative for + * processing responses from Redis compared to the standard pure-PHP classes. + * Differences in speed when dealing with short inline responses are practically + * nonexistent, the actual speed boost is for big multibulk responses when this + * protocol processor can parse and return responses very fast. + * + * For instructions on how to build and install the phpiredis extension, please + * consult the repository of the project. + * + * The connection parameters supported by this class are: + * + * - scheme: it can be either 'redis', 'tcp' or 'unix'. + * - host: hostname or IP address of the server. + * - port: TCP port of the server. + * - path: path of a UNIX domain socket when scheme is 'unix'. + * - timeout: timeout to perform the connection. + * - read_write_timeout: timeout of read / write operations. + * - async_connect: performs the connection asynchronously. + * - tcp_nodelay: enables or disables Nagle's algorithm for coalescing. + * - persistent: the connection is left intact after a GC collection. + * + * @link https://github.com/nrk/phpiredis + * + * @author Daniele Alessandri + */ +class PhpiredisStreamConnection extends StreamConnection +{ + private $reader; + + /** + * {@inheritdoc} + */ + public function __construct(ParametersInterface $parameters) + { + $this->assertExtensions(); + + parent::__construct($parameters); + + $this->reader = $this->createReader(); + } + + /** + * {@inheritdoc} + */ + public function __destruct() + { + phpiredis_reader_destroy($this->reader); + + parent::__destruct(); + } + + /** + * Checks if the phpiredis extension is loaded in PHP. + */ + private function assertExtensions() + { + if (!extension_loaded('phpiredis')) { + throw new NotSupportedException( + 'The "phpiredis" extension is required by this connection backend.' + ); + } + } + + /** + * {@inheritdoc} + */ + protected function assertSslSupport(ParametersInterface $parameters) + { + throw new \InvalidArgumentException('SSL encryption is not supported by this connection backend.'); + } + + /** + * {@inheritdoc} + */ + protected function createStreamSocket(ParametersInterface $parameters, $address, $flags, $context = null) + { + $socket = null; + $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0); + + $resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags); + + if (!$resource) { + $this->onConnectionError(trim($errstr), $errno); + } + + if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) { + $rwtimeout = (float) $parameters->read_write_timeout; + $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1; + + $timeout = array( + 'sec' => $timeoutSeconds = floor($rwtimeout), + 'usec' => ($rwtimeout - $timeoutSeconds) * 1000000, + ); + + $socket = $socket ?: socket_import_stream($resource); + @socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout); + @socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout); + } + + if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) { + $socket = $socket ?: socket_import_stream($resource); + socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay); + } + + return $resource; + } + + /** + * Creates a new instance of the protocol reader resource. + * + * @return resource + */ + private function createReader() + { + $reader = phpiredis_reader_create(); + + phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); + phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); + + return $reader; + } + + /** + * Returns the underlying protocol reader resource. + * + * @return resource + */ + protected function getReader() + { + return $this->reader; + } + + /** + * Returns the handler used by the protocol reader for inline responses. + * + * @return \Closure + */ + protected function getStatusHandler() + { + static $statusHandler; + + if (!$statusHandler) { + $statusHandler = function ($payload) { + return StatusResponse::get($payload); + }; + } + + return $statusHandler; + } + + /** + * Returns the handler used by the protocol reader for error responses. + * + * @return \Closure + */ + protected function getErrorHandler() + { + static $errorHandler; + + if (!$errorHandler) { + $errorHandler = function ($errorMessage) { + return new ErrorResponse($errorMessage); + }; + } + + return $errorHandler; + } + + /** + * {@inheritdoc} + */ + public function read() + { + $socket = $this->getResource(); + $reader = $this->reader; + + while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) { + $buffer = stream_socket_recvfrom($socket, 4096); + + if ($buffer === false || $buffer === '') { + $this->onConnectionError('Error while reading bytes from the server.'); + } + + phpiredis_reader_feed($reader, $buffer); + } + + if ($state === PHPIREDIS_READER_STATE_COMPLETE) { + return phpiredis_reader_get_reply($reader); + } else { + $this->onProtocolError(phpiredis_reader_get_error($reader)); + + return; + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $arguments = $command->getArguments(); + array_unshift($arguments, $command->getId()); + + $this->write(phpiredis_format_command($arguments)); + } + + /** + * {@inheritdoc} + */ + public function __wakeup() + { + $this->assertExtensions(); + $this->reader = $this->createReader(); + } +} diff --git a/vendor/predis/predis/src/Connection/StreamConnection.php b/vendor/predis/predis/src/Connection/StreamConnection.php new file mode 100644 index 0000000..9c26272 --- /dev/null +++ b/vendor/predis/predis/src/Connection/StreamConnection.php @@ -0,0 +1,396 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; +use Predis\Response\Error as ErrorResponse; +use Predis\Response\ErrorInterface as ErrorResponseInterface; +use Predis\Response\Status as StatusResponse; + +/** + * Standard connection to Redis servers implemented on top of PHP's streams. + * The connection parameters supported by this class are:. + * + * - scheme: it can be either 'redis', 'tcp', 'rediss', 'tls' or 'unix'. + * - host: hostname or IP address of the server. + * - port: TCP port of the server. + * - path: path of a UNIX domain socket when scheme is 'unix'. + * - timeout: timeout to perform the connection (default is 5 seconds). + * - read_write_timeout: timeout of read / write operations. + * - async_connect: performs the connection asynchronously. + * - tcp_nodelay: enables or disables Nagle's algorithm for coalescing. + * - persistent: the connection is left intact after a GC collection. + * - ssl: context options array (see http://php.net/manual/en/context.ssl.php) + * + * @author Daniele Alessandri + */ +class StreamConnection extends AbstractConnection +{ + /** + * Disconnects from the server and destroys the underlying resource when the + * garbage collector kicks in only if the connection has not been marked as + * persistent. + */ + public function __destruct() + { + if (isset($this->parameters->persistent) && $this->parameters->persistent) { + return; + } + + $this->disconnect(); + } + + /** + * {@inheritdoc} + */ + protected function assertParameters(ParametersInterface $parameters) + { + switch ($parameters->scheme) { + case 'tcp': + case 'redis': + case 'unix': + break; + + case 'tls': + case 'rediss': + $this->assertSslSupport($parameters); + break; + + default: + throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'."); + } + + return $parameters; + } + + /** + * Checks needed conditions for SSL-encrypted connections. + * + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @throws \InvalidArgumentException + */ + protected function assertSslSupport(ParametersInterface $parameters) + { + if ( + filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN) && + version_compare(PHP_VERSION, '7.0.0beta') < 0 + ) { + throw new \InvalidArgumentException('Persistent SSL connections require PHP >= 7.0.0.'); + } + } + + /** + * {@inheritdoc} + */ + protected function createResource() + { + switch ($this->parameters->scheme) { + case 'tcp': + case 'redis': + return $this->tcpStreamInitializer($this->parameters); + + case 'unix': + return $this->unixStreamInitializer($this->parameters); + + case 'tls': + case 'rediss': + return $this->tlsStreamInitializer($this->parameters); + + default: + throw new \InvalidArgumentException("Invalid scheme: '{$this->parameters->scheme}'."); + } + } + + /** + * Creates a connected stream socket resource. + * + * @param ParametersInterface $parameters Connection parameters. + * @param string $address Address for stream_socket_client(). + * @param int $flags Flags for stream_socket_client(). + * + * @return resource + */ + protected function createStreamSocket(ParametersInterface $parameters, $address, $flags) + { + $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0); + + if (!$resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags)) { + $this->onConnectionError(trim($errstr), $errno); + } + + if (isset($parameters->read_write_timeout)) { + $rwtimeout = (float) $parameters->read_write_timeout; + $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1; + $timeoutSeconds = floor($rwtimeout); + $timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000; + stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds); + } + + if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) { + $socket = socket_import_stream($resource); + socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay); + } + + return $resource; + } + + /** + * Initializes a TCP stream resource. + * + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @return resource + */ + protected function tcpStreamInitializer(ParametersInterface $parameters) + { + if (!filter_var($parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $address = "tcp://$parameters->host:$parameters->port"; + } else { + $address = "tcp://[$parameters->host]:$parameters->port"; + } + + $flags = STREAM_CLIENT_CONNECT; + + if (isset($parameters->async_connect) && $parameters->async_connect) { + $flags |= STREAM_CLIENT_ASYNC_CONNECT; + } + + if (isset($parameters->persistent)) { + if (false !== $persistent = filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) { + $flags |= STREAM_CLIENT_PERSISTENT; + + if ($persistent === null) { + $address = "{$address}/{$parameters->persistent}"; + } + } + } + + $resource = $this->createStreamSocket($parameters, $address, $flags); + + return $resource; + } + + /** + * Initializes a UNIX stream resource. + * + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @return resource + */ + protected function unixStreamInitializer(ParametersInterface $parameters) + { + if (!isset($parameters->path)) { + throw new \InvalidArgumentException('Missing UNIX domain socket path.'); + } + + $flags = STREAM_CLIENT_CONNECT; + + if (isset($parameters->persistent)) { + if (false !== $persistent = filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) { + $flags |= STREAM_CLIENT_PERSISTENT; + + if ($persistent === null) { + throw new \InvalidArgumentException( + 'Persistent connection IDs are not supported when using UNIX domain sockets.' + ); + } + } + } + + $resource = $this->createStreamSocket($parameters, "unix://{$parameters->path}", $flags); + + return $resource; + } + + /** + * Initializes a SSL-encrypted TCP stream resource. + * + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @return resource + */ + protected function tlsStreamInitializer(ParametersInterface $parameters) + { + $resource = $this->tcpStreamInitializer($parameters); + $metadata = stream_get_meta_data($resource); + + // Detect if crypto mode is already enabled for this stream (PHP >= 7.0.0). + if (isset($metadata['crypto'])) { + return $resource; + } + + if (is_array($parameters->ssl)) { + $options = $parameters->ssl; + } else { + $options = array(); + } + + if (!isset($options['crypto_type'])) { + $options['crypto_type'] = STREAM_CRYPTO_METHOD_TLS_CLIENT; + } + + if (!stream_context_set_option($resource, array('ssl' => $options))) { + $this->onConnectionError('Error while setting SSL context options'); + } + + if (!stream_socket_enable_crypto($resource, true, $options['crypto_type'])) { + $this->onConnectionError('Error while switching to encrypted communication'); + } + + return $resource; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (parent::connect() && $this->initCommands) { + foreach ($this->initCommands as $command) { + $response = $this->executeCommand($command); + + if ($response instanceof ErrorResponseInterface) { + $this->onConnectionError("`{$command->getId()}` failed: $response", 0); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + if ($this->isConnected()) { + fclose($this->getResource()); + parent::disconnect(); + } + } + + /** + * Performs a write operation over the stream of the buffer containing a + * command serialized with the Redis wire protocol. + * + * @param string $buffer Representation of a command in the Redis wire protocol. + */ + protected function write($buffer) + { + $socket = $this->getResource(); + + while (($length = strlen($buffer)) > 0) { + $written = @fwrite($socket, $buffer); + + if ($length === $written) { + return; + } + + if ($written === false || $written === 0) { + $this->onConnectionError('Error while writing bytes to the server.'); + } + + $buffer = substr($buffer, $written); + } + } + + /** + * {@inheritdoc} + */ + public function read() + { + $socket = $this->getResource(); + $chunk = fgets($socket); + + if ($chunk === false || $chunk === '') { + $this->onConnectionError('Error while reading line from the server.'); + } + + $prefix = $chunk[0]; + $payload = substr($chunk, 1, -2); + + switch ($prefix) { + case '+': + return StatusResponse::get($payload); + + case '$': + $size = (int) $payload; + + if ($size === -1) { + return; + } + + $bulkData = ''; + $bytesLeft = ($size += 2); + + do { + $chunk = fread($socket, min($bytesLeft, 4096)); + + if ($chunk === false || $chunk === '') { + $this->onConnectionError('Error while reading bytes from the server.'); + } + + $bulkData .= $chunk; + $bytesLeft = $size - strlen($bulkData); + } while ($bytesLeft > 0); + + return substr($bulkData, 0, -2); + + case '*': + $count = (int) $payload; + + if ($count === -1) { + return; + } + + $multibulk = array(); + + for ($i = 0; $i < $count; ++$i) { + $multibulk[$i] = $this->read(); + } + + return $multibulk; + + case ':': + $integer = (int) $payload; + return $integer == $payload ? $integer : $payload; + + case '-': + return new ErrorResponse($payload); + + default: + $this->onProtocolError("Unknown response prefix: '$prefix'."); + + return; + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $commandID = $command->getId(); + $arguments = $command->getArguments(); + + $cmdlen = strlen($commandID); + $reqlen = count($arguments) + 1; + + $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n"; + + foreach ($arguments as $argument) { + $arglen = strlen($argument); + $buffer .= "\${$arglen}\r\n{$argument}\r\n"; + } + + $this->write($buffer); + } +} diff --git a/vendor/predis/predis/src/Connection/WebdisConnection.php b/vendor/predis/predis/src/Connection/WebdisConnection.php new file mode 100644 index 0000000..c8d6e50 --- /dev/null +++ b/vendor/predis/predis/src/Connection/WebdisConnection.php @@ -0,0 +1,366 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; +use Predis\NotSupportedException; +use Predis\Protocol\ProtocolException; +use Predis\Response\Error as ErrorResponse; +use Predis\Response\Status as StatusResponse; + +/** + * This class implements a Predis connection that actually talks with Webdis + * instead of connecting directly to Redis. It relies on the cURL extension to + * communicate with the web server and the phpiredis extension to parse the + * protocol for responses returned in the http response bodies. + * + * Some features are not yet available or they simply cannot be implemented: + * - Pipelining commands. + * - Publish / Subscribe. + * - MULTI / EXEC transactions (not yet supported by Webdis). + * + * The connection parameters supported by this class are: + * + * - scheme: must be 'http'. + * - host: hostname or IP address of the server. + * - port: TCP port of the server. + * - timeout: timeout to perform the connection (default is 5 seconds). + * - user: username for authentication. + * - pass: password for authentication. + * + * @link http://webd.is + * @link http://github.com/nicolasff/webdis + * @link http://github.com/seppo0010/phpiredis + * + * @author Daniele Alessandri + */ +class WebdisConnection implements NodeConnectionInterface +{ + private $parameters; + private $resource; + private $reader; + + /** + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @throws \InvalidArgumentException + */ + public function __construct(ParametersInterface $parameters) + { + $this->assertExtensions(); + + if ($parameters->scheme !== 'http') { + throw new \InvalidArgumentException("Invalid scheme: '{$parameters->scheme}'."); + } + + $this->parameters = $parameters; + + $this->resource = $this->createCurl(); + $this->reader = $this->createReader(); + } + + /** + * Frees the underlying cURL and protocol reader resources when the garbage + * collector kicks in. + */ + public function __destruct() + { + curl_close($this->resource); + phpiredis_reader_destroy($this->reader); + } + + /** + * Helper method used to throw on unsupported methods. + * + * @param string $method Name of the unsupported method. + * + * @throws NotSupportedException + */ + private function throwNotSupportedException($method) + { + $class = __CLASS__; + throw new NotSupportedException("The method $class::$method() is not supported."); + } + + /** + * Checks if the cURL and phpiredis extensions are loaded in PHP. + */ + private function assertExtensions() + { + if (!extension_loaded('curl')) { + throw new NotSupportedException( + 'The "curl" extension is required by this connection backend.' + ); + } + + if (!extension_loaded('phpiredis')) { + throw new NotSupportedException( + 'The "phpiredis" extension is required by this connection backend.' + ); + } + } + + /** + * Initializes cURL. + * + * @return resource + */ + private function createCurl() + { + $parameters = $this->getParameters(); + $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0) * 1000; + + if (filter_var($host = $parameters->host, FILTER_VALIDATE_IP)) { + $host = "[$host]"; + } + + $options = array( + CURLOPT_FAILONERROR => true, + CURLOPT_CONNECTTIMEOUT_MS => $timeout, + CURLOPT_URL => "$parameters->scheme://$host:$parameters->port", + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_POST => true, + CURLOPT_WRITEFUNCTION => array($this, 'feedReader'), + ); + + if (isset($parameters->user, $parameters->pass)) { + $options[CURLOPT_USERPWD] = "{$parameters->user}:{$parameters->pass}"; + } + + curl_setopt_array($resource = curl_init(), $options); + + return $resource; + } + + /** + * Initializes the phpiredis protocol reader. + * + * @return resource + */ + private function createReader() + { + $reader = phpiredis_reader_create(); + + phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); + phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); + + return $reader; + } + + /** + * Returns the handler used by the protocol reader for inline responses. + * + * @return \Closure + */ + protected function getStatusHandler() + { + static $statusHandler; + + if (!$statusHandler) { + $statusHandler = function ($payload) { + return StatusResponse::get($payload); + }; + } + + return $statusHandler; + } + + /** + * Returns the handler used by the protocol reader for error responses. + * + * @return \Closure + */ + protected function getErrorHandler() + { + static $errorHandler; + + if (!$errorHandler) { + $errorHandler = function ($errorMessage) { + return new ErrorResponse($errorMessage); + }; + } + + return $errorHandler; + } + + /** + * Feeds the phpredis reader resource with the data read from the network. + * + * @param resource $resource Reader resource. + * @param string $buffer Buffer of data read from a connection. + * + * @return int + */ + protected function feedReader($resource, $buffer) + { + phpiredis_reader_feed($this->reader, $buffer); + + return strlen($buffer); + } + + /** + * {@inheritdoc} + */ + public function connect() + { + // NOOP + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + // NOOP + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + return true; + } + + /** + * Checks if the specified command is supported by this connection class. + * + * @param CommandInterface $command Command instance. + * + * @throws NotSupportedException + * + * @return string + */ + protected function getCommandId(CommandInterface $command) + { + switch ($commandID = $command->getId()) { + case 'AUTH': + case 'SELECT': + case 'MULTI': + case 'EXEC': + case 'WATCH': + case 'UNWATCH': + case 'DISCARD': + case 'MONITOR': + throw new NotSupportedException("Command '$commandID' is not allowed by Webdis."); + + default: + return $commandID; + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->throwNotSupportedException(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + $this->throwNotSupportedException(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + $resource = $this->resource; + $commandId = $this->getCommandId($command); + + if ($arguments = $command->getArguments()) { + $arguments = implode('/', array_map('urlencode', $arguments)); + $serializedCommand = "$commandId/$arguments.raw"; + } else { + $serializedCommand = "$commandId.raw"; + } + + curl_setopt($resource, CURLOPT_POSTFIELDS, $serializedCommand); + + if (curl_exec($resource) === false) { + $error = curl_error($resource); + $errno = curl_errno($resource); + + throw new ConnectionException($this, trim($error), $errno); + } + + if (phpiredis_reader_get_state($this->reader) !== PHPIREDIS_READER_STATE_COMPLETE) { + throw new ProtocolException($this, phpiredis_reader_get_error($this->reader)); + } + + return phpiredis_reader_get_reply($this->reader); + } + + /** + * {@inheritdoc} + */ + public function getResource() + { + return $this->resource; + } + + /** + * {@inheritdoc} + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * {@inheritdoc} + */ + public function addConnectCommand(CommandInterface $command) + { + $this->throwNotSupportedException(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function read() + { + $this->throwNotSupportedException(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return "{$this->parameters->host}:{$this->parameters->port}"; + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array('parameters'); + } + + /** + * {@inheritdoc} + */ + public function __wakeup() + { + $this->assertExtensions(); + + $this->resource = $this->createCurl(); + $this->reader = $this->createReader(); + } +} diff --git a/vendor/predis/predis/src/Monitor/Consumer.php b/vendor/predis/predis/src/Monitor/Consumer.php new file mode 100644 index 0000000..aaf645b --- /dev/null +++ b/vendor/predis/predis/src/Monitor/Consumer.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Monitor; + +use Predis\ClientInterface; +use Predis\Connection\AggregateConnectionInterface; +use Predis\NotSupportedException; + +/** + * Redis MONITOR consumer. + * + * @author Daniele Alessandri + */ +class Consumer implements \Iterator +{ + private $client; + private $valid; + private $position; + + /** + * @param ClientInterface $client Client instance used by the consumer. + */ + public function __construct(ClientInterface $client) + { + $this->assertClient($client); + + $this->client = $client; + + $this->start(); + } + + /** + * Automatically stops the consumer when the garbage collector kicks in. + */ + public function __destruct() + { + $this->stop(); + } + + /** + * Checks if the passed client instance satisfies the required conditions + * needed to initialize a monitor consumer. + * + * @param ClientInterface $client Client instance used by the consumer. + * + * @throws NotSupportedException + */ + private function assertClient(ClientInterface $client) + { + if ($client->getConnection() instanceof AggregateConnectionInterface) { + throw new NotSupportedException( + 'Cannot initialize a monitor consumer over aggregate connections.' + ); + } + + if ($client->getProfile()->supportsCommand('MONITOR') === false) { + throw new NotSupportedException("The current profile does not support 'MONITOR'."); + } + } + + /** + * Initializes the consumer and sends the MONITOR command to the server. + */ + protected function start() + { + $this->client->executeCommand( + $this->client->createCommand('MONITOR') + ); + $this->valid = true; + } + + /** + * Stops the consumer. Internally this is done by disconnecting from server + * since there is no way to terminate the stream initialized by MONITOR. + */ + public function stop() + { + $this->client->disconnect(); + $this->valid = false; + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + // NOOP + } + + /** + * Returns the last message payload retrieved from the server. + * + * @return object + */ + public function current() + { + return $this->getValue(); + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + public function next() + { + ++$this->position; + } + + /** + * Checks if the the consumer is still in a valid state to continue. + * + * @return bool + */ + public function valid() + { + return $this->valid; + } + + /** + * Waits for a new message from the server generated by MONITOR and returns + * it when available. + * + * @return object + */ + private function getValue() + { + $database = 0; + $client = null; + $event = $this->client->getConnection()->read(); + + $callback = function ($matches) use (&$database, &$client) { + if (2 === $count = count($matches)) { + // Redis <= 2.4 + $database = (int) $matches[1]; + } + + if (4 === $count) { + // Redis >= 2.6 + $database = (int) $matches[2]; + $client = $matches[3]; + } + + return ' '; + }; + + $event = preg_replace_callback('/ \(db (\d+)\) | \[(\d+) (.*?)\] /', $callback, $event, 1); + @list($timestamp, $command, $arguments) = explode(' ', $event, 3); + + return (object) array( + 'timestamp' => (float) $timestamp, + 'database' => $database, + 'client' => $client, + 'command' => substr($command, 1, -1), + 'arguments' => $arguments, + ); + } +} diff --git a/vendor/predis/predis/src/NotSupportedException.php b/vendor/predis/predis/src/NotSupportedException.php new file mode 100644 index 0000000..be82aba --- /dev/null +++ b/vendor/predis/predis/src/NotSupportedException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis; + +/** + * Exception class thrown when trying to use features not supported by certain + * classes or abstractions of Predis. + * + * @author Daniele Alessandri + */ +class NotSupportedException extends PredisException +{ +} diff --git a/vendor/predis/predis/src/Pipeline/Atomic.php b/vendor/predis/predis/src/Pipeline/Atomic.php new file mode 100644 index 0000000..1c9c92a --- /dev/null +++ b/vendor/predis/predis/src/Pipeline/Atomic.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Pipeline; + +use Predis\ClientException; +use Predis\ClientInterface; +use Predis\Connection\ConnectionInterface; +use Predis\Connection\NodeConnectionInterface; +use Predis\Response\ErrorInterface as ErrorResponseInterface; +use Predis\Response\ResponseInterface; +use Predis\Response\ServerException; + +/** + * Command pipeline wrapped into a MULTI / EXEC transaction. + * + * @author Daniele Alessandri + */ +class Atomic extends Pipeline +{ + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client) + { + if (!$client->getProfile()->supportsCommands(array('multi', 'exec', 'discard'))) { + throw new ClientException( + "The current profile does not support 'MULTI', 'EXEC' and 'DISCARD'." + ); + } + + parent::__construct($client); + } + + /** + * {@inheritdoc} + */ + protected function getConnection() + { + $connection = $this->getClient()->getConnection(); + + if (!$connection instanceof NodeConnectionInterface) { + $class = __CLASS__; + + throw new ClientException("The class '$class' does not support aggregate connections."); + } + + return $connection; + } + + /** + * {@inheritdoc} + */ + protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) + { + $profile = $this->getClient()->getProfile(); + $connection->executeCommand($profile->createCommand('multi')); + + foreach ($commands as $command) { + $connection->writeRequest($command); + } + + foreach ($commands as $command) { + $response = $connection->readResponse($command); + + if ($response instanceof ErrorResponseInterface) { + $connection->executeCommand($profile->createCommand('discard')); + throw new ServerException($response->getMessage()); + } + } + + $executed = $connection->executeCommand($profile->createCommand('exec')); + + if (!isset($executed)) { + // TODO: should be throwing a more appropriate exception. + throw new ClientException( + 'The underlying transaction has been aborted by the server.' + ); + } + + if (count($executed) !== count($commands)) { + $expected = count($commands); + $received = count($executed); + + throw new ClientException( + "Invalid number of responses [expected $expected, received $received]." + ); + } + + $responses = array(); + $sizeOfPipe = count($commands); + $exceptions = $this->throwServerExceptions(); + + for ($i = 0; $i < $sizeOfPipe; ++$i) { + $command = $commands->dequeue(); + $response = $executed[$i]; + + if (!$response instanceof ResponseInterface) { + $responses[] = $command->parseResponse($response); + } elseif ($response instanceof ErrorResponseInterface && $exceptions) { + $this->exception($connection, $response); + } else { + $responses[] = $response; + } + + unset($executed[$i]); + } + + return $responses; + } +} diff --git a/vendor/predis/predis/src/Pipeline/ConnectionErrorProof.php b/vendor/predis/predis/src/Pipeline/ConnectionErrorProof.php new file mode 100644 index 0000000..d3bc732 --- /dev/null +++ b/vendor/predis/predis/src/Pipeline/ConnectionErrorProof.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Pipeline; + +use Predis\CommunicationException; +use Predis\Connection\Aggregate\ClusterInterface; +use Predis\Connection\ConnectionInterface; +use Predis\Connection\NodeConnectionInterface; +use Predis\NotSupportedException; + +/** + * Command pipeline that does not throw exceptions on connection errors, but + * returns the exception instances as the rest of the response elements. + * + * @todo Awful naming! + * + * @author Daniele Alessandri + */ +class ConnectionErrorProof extends Pipeline +{ + /** + * {@inheritdoc} + */ + protected function getConnection() + { + return $this->getClient()->getConnection(); + } + + /** + * {@inheritdoc} + */ + protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) + { + if ($connection instanceof NodeConnectionInterface) { + return $this->executeSingleNode($connection, $commands); + } elseif ($connection instanceof ClusterInterface) { + return $this->executeCluster($connection, $commands); + } else { + $class = get_class($connection); + + throw new NotSupportedException("The connection class '$class' is not supported."); + } + } + + /** + * {@inheritdoc} + */ + protected function executeSingleNode(NodeConnectionInterface $connection, \SplQueue $commands) + { + $responses = array(); + $sizeOfPipe = count($commands); + + foreach ($commands as $command) { + try { + $connection->writeRequest($command); + } catch (CommunicationException $exception) { + return array_fill(0, $sizeOfPipe, $exception); + } + } + + for ($i = 0; $i < $sizeOfPipe; ++$i) { + $command = $commands->dequeue(); + + try { + $responses[$i] = $connection->readResponse($command); + } catch (CommunicationException $exception) { + $add = count($commands) - count($responses); + $responses = array_merge($responses, array_fill(0, $add, $exception)); + + break; + } + } + + return $responses; + } + + /** + * {@inheritdoc} + */ + protected function executeCluster(ClusterInterface $connection, \SplQueue $commands) + { + $responses = array(); + $sizeOfPipe = count($commands); + $exceptions = array(); + + foreach ($commands as $command) { + $cmdConnection = $connection->getConnection($command); + + if (isset($exceptions[spl_object_hash($cmdConnection)])) { + continue; + } + + try { + $cmdConnection->writeRequest($command); + } catch (CommunicationException $exception) { + $exceptions[spl_object_hash($cmdConnection)] = $exception; + } + } + + for ($i = 0; $i < $sizeOfPipe; ++$i) { + $command = $commands->dequeue(); + + $cmdConnection = $connection->getConnection($command); + $connectionHash = spl_object_hash($cmdConnection); + + if (isset($exceptions[$connectionHash])) { + $responses[$i] = $exceptions[$connectionHash]; + continue; + } + + try { + $responses[$i] = $cmdConnection->readResponse($command); + } catch (CommunicationException $exception) { + $responses[$i] = $exception; + $exceptions[$connectionHash] = $exception; + } + } + + return $responses; + } +} diff --git a/vendor/predis/predis/src/Pipeline/FireAndForget.php b/vendor/predis/predis/src/Pipeline/FireAndForget.php new file mode 100644 index 0000000..95a062b --- /dev/null +++ b/vendor/predis/predis/src/Pipeline/FireAndForget.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 Predis\Pipeline; + +use Predis\Connection\ConnectionInterface; + +/** + * Command pipeline that writes commands to the servers but discards responses. + * + * @author Daniele Alessandri + */ +class FireAndForget extends Pipeline +{ + /** + * {@inheritdoc} + */ + protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) + { + while (!$commands->isEmpty()) { + $connection->writeRequest($commands->dequeue()); + } + + $connection->disconnect(); + + return array(); + } +} diff --git a/vendor/predis/predis/src/Pipeline/Pipeline.php b/vendor/predis/predis/src/Pipeline/Pipeline.php new file mode 100644 index 0000000..cf9c59e --- /dev/null +++ b/vendor/predis/predis/src/Pipeline/Pipeline.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Pipeline; + +use Predis\ClientContextInterface; +use Predis\ClientException; +use Predis\ClientInterface; +use Predis\Command\CommandInterface; +use Predis\Connection\Aggregate\ReplicationInterface; +use Predis\Connection\ConnectionInterface; +use Predis\Response\ErrorInterface as ErrorResponseInterface; +use Predis\Response\ResponseInterface; +use Predis\Response\ServerException; + +/** + * Implementation of a command pipeline in which write and read operations of + * Redis commands are pipelined to alleviate the effects of network round-trips. + * + * {@inheritdoc} + * + * @author Daniele Alessandri + */ +class Pipeline implements ClientContextInterface +{ + private $client; + private $pipeline; + + private $responses = array(); + private $running = false; + + /** + * @param ClientInterface $client Client instance used by the context. + */ + public function __construct(ClientInterface $client) + { + $this->client = $client; + $this->pipeline = new \SplQueue(); + } + + /** + * Queues a command into the pipeline buffer. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + * + * @return $this + */ + public function __call($method, $arguments) + { + $command = $this->client->createCommand($method, $arguments); + $this->recordCommand($command); + + return $this; + } + + /** + * Queues a command instance into the pipeline buffer. + * + * @param CommandInterface $command Command to be queued in the buffer. + */ + protected function recordCommand(CommandInterface $command) + { + $this->pipeline->enqueue($command); + } + + /** + * Queues a command instance into the pipeline buffer. + * + * @param CommandInterface $command Command instance to be queued in the buffer. + * + * @return $this + */ + public function executeCommand(CommandInterface $command) + { + $this->recordCommand($command); + + return $this; + } + + /** + * Throws an exception on -ERR responses returned by Redis. + * + * @param ConnectionInterface $connection Redis connection that returned the error. + * @param ErrorResponseInterface $response Instance of the error response. + * + * @throws ServerException + */ + protected function exception(ConnectionInterface $connection, ErrorResponseInterface $response) + { + $connection->disconnect(); + $message = $response->getMessage(); + + throw new ServerException($message); + } + + /** + * Returns the underlying connection to be used by the pipeline. + * + * @return ConnectionInterface + */ + protected function getConnection() + { + $connection = $this->getClient()->getConnection(); + + if ($connection instanceof ReplicationInterface) { + $connection->switchTo('master'); + } + + return $connection; + } + + /** + * Implements the logic to flush the queued commands and read the responses + * from the current connection. + * + * @param ConnectionInterface $connection Current connection instance. + * @param \SplQueue $commands Queued commands. + * + * @return array + */ + protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) + { + foreach ($commands as $command) { + $connection->writeRequest($command); + } + + $responses = array(); + $exceptions = $this->throwServerExceptions(); + + while (!$commands->isEmpty()) { + $command = $commands->dequeue(); + $response = $connection->readResponse($command); + + if (!$response instanceof ResponseInterface) { + $responses[] = $command->parseResponse($response); + } elseif ($response instanceof ErrorResponseInterface && $exceptions) { + $this->exception($connection, $response); + } else { + $responses[] = $response; + } + } + + return $responses; + } + + /** + * Flushes the buffer holding all of the commands queued so far. + * + * @param bool $send Specifies if the commands in the buffer should be sent to Redis. + * + * @return $this + */ + public function flushPipeline($send = true) + { + if ($send && !$this->pipeline->isEmpty()) { + $responses = $this->executePipeline($this->getConnection(), $this->pipeline); + $this->responses = array_merge($this->responses, $responses); + } else { + $this->pipeline = new \SplQueue(); + } + + return $this; + } + + /** + * Marks the running status of the pipeline. + * + * @param bool $bool Sets the running status of the pipeline. + * + * @throws ClientException + */ + private function setRunning($bool) + { + if ($bool && $this->running) { + throw new ClientException('The current pipeline context is already being executed.'); + } + + $this->running = $bool; + } + + /** + * Handles the actual execution of the whole pipeline. + * + * @param mixed $callable Optional callback for execution. + * + * @throws \Exception + * @throws \InvalidArgumentException + * + * @return array + */ + public function execute($callable = null) + { + if ($callable && !is_callable($callable)) { + throw new \InvalidArgumentException('The argument must be a callable object.'); + } + + $exception = null; + $this->setRunning(true); + + try { + if ($callable) { + call_user_func($callable, $this); + } + + $this->flushPipeline(); + } catch (\Exception $exception) { + // NOOP + } + + $this->setRunning(false); + + if ($exception) { + throw $exception; + } + + return $this->responses; + } + + /** + * Returns if the pipeline should throw exceptions on server errors. + * + * @return bool + */ + protected function throwServerExceptions() + { + return (bool) $this->client->getOptions()->exceptions; + } + + /** + * Returns the underlying client instance used by the pipeline object. + * + * @return ClientInterface + */ + public function getClient() + { + return $this->client; + } +} diff --git a/vendor/predis/predis/src/PredisException.php b/vendor/predis/predis/src/PredisException.php new file mode 100644 index 0000000..122bde1 --- /dev/null +++ b/vendor/predis/predis/src/PredisException.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 Predis; + +/** + * Base exception class for Predis-related errors. + * + * @author Daniele Alessandri + */ +abstract class PredisException extends \Exception +{ +} diff --git a/vendor/predis/predis/src/Profile/Factory.php b/vendor/predis/predis/src/Profile/Factory.php new file mode 100644 index 0000000..d4907a2 --- /dev/null +++ b/vendor/predis/predis/src/Profile/Factory.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 Predis\Profile; + +use Predis\ClientException; + +/** + * Factory class for creating profile instances from strings. + * + * @author Daniele Alessandri + */ +final class Factory +{ + private static $profiles = array( + '2.0' => 'Predis\Profile\RedisVersion200', + '2.2' => 'Predis\Profile\RedisVersion220', + '2.4' => 'Predis\Profile\RedisVersion240', + '2.6' => 'Predis\Profile\RedisVersion260', + '2.8' => 'Predis\Profile\RedisVersion280', + '3.0' => 'Predis\Profile\RedisVersion300', + '3.2' => 'Predis\Profile\RedisVersion320', + 'dev' => 'Predis\Profile\RedisUnstable', + 'default' => 'Predis\Profile\RedisVersion320', + ); + + /** + * + */ + private function __construct() + { + // NOOP + } + + /** + * Returns the default server profile. + * + * @return ProfileInterface + */ + public static function getDefault() + { + return self::get('default'); + } + + /** + * Returns the development server profile. + * + * @return ProfileInterface + */ + public static function getDevelopment() + { + return self::get('dev'); + } + + /** + * Registers a new server profile. + * + * @param string $alias Profile version or alias. + * @param string $class FQN of a class implementing Predis\Profile\ProfileInterface. + * + * @throws \InvalidArgumentException + */ + public static function define($alias, $class) + { + $reflection = new \ReflectionClass($class); + + if (!$reflection->isSubclassOf('Predis\Profile\ProfileInterface')) { + throw new \InvalidArgumentException("The class '$class' is not a valid profile class."); + } + + self::$profiles[$alias] = $class; + } + + /** + * Returns the specified server profile. + * + * @param string $version Profile version or alias. + * + * @throws ClientException + * + * @return ProfileInterface + */ + public static function get($version) + { + if (!isset(self::$profiles[$version])) { + throw new ClientException("Unknown server profile: '$version'."); + } + + $profile = self::$profiles[$version]; + + return new $profile(); + } +} diff --git a/vendor/predis/predis/src/Profile/ProfileInterface.php b/vendor/predis/predis/src/Profile/ProfileInterface.php new file mode 100644 index 0000000..abe71aa --- /dev/null +++ b/vendor/predis/predis/src/Profile/ProfileInterface.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 Predis\Profile; + +use Predis\Command\CommandInterface; + +/** + * A profile defines all the features and commands supported by certain versions + * of Redis. Instances of Predis\Client should use a server profile matching the + * version of Redis being used. + * + * @author Daniele Alessandri + */ +interface ProfileInterface +{ + /** + * Returns the profile version corresponding to the Redis version. + * + * @return string + */ + public function getVersion(); + + /** + * Checks if the profile supports the specified command. + * + * @param string $commandID Command ID. + * + * @return bool + */ + public function supportsCommand($commandID); + + /** + * Checks if the profile supports the specified list of commands. + * + * @param array $commandIDs List of command IDs. + * + * @return string + */ + public function supportsCommands(array $commandIDs); + + /** + * Creates a new command instance. + * + * @param string $commandID Command ID. + * @param array $arguments Arguments for the command. + * + * @return CommandInterface + */ + public function createCommand($commandID, array $arguments = array()); +} diff --git a/vendor/predis/predis/src/Profile/RedisProfile.php b/vendor/predis/predis/src/Profile/RedisProfile.php new file mode 100644 index 0000000..3ef3168 --- /dev/null +++ b/vendor/predis/predis/src/Profile/RedisProfile.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +use Predis\ClientException; +use Predis\Command\Processor\ProcessorInterface; + +/** + * Base class implementing common functionalities for Redis server profiles. + * + * @author Daniele Alessandri + */ +abstract class RedisProfile implements ProfileInterface +{ + private $commands; + private $processor; + + /** + * + */ + public function __construct() + { + $this->commands = $this->getSupportedCommands(); + } + + /** + * Returns a map of all the commands supported by the profile and their + * actual PHP classes. + * + * @return array + */ + abstract protected function getSupportedCommands(); + + /** + * {@inheritdoc} + */ + public function supportsCommand($commandID) + { + return isset($this->commands[strtoupper($commandID)]); + } + + /** + * {@inheritdoc} + */ + public function supportsCommands(array $commandIDs) + { + foreach ($commandIDs as $commandID) { + if (!$this->supportsCommand($commandID)) { + return false; + } + } + + return true; + } + + /** + * Returns the fully-qualified name of a class representing the specified + * command ID registered in the current server profile. + * + * @param string $commandID Command ID. + * + * @return string|null + */ + public function getCommandClass($commandID) + { + if (isset($this->commands[$commandID = strtoupper($commandID)])) { + return $this->commands[$commandID]; + } + } + + /** + * {@inheritdoc} + */ + public function createCommand($commandID, array $arguments = array()) + { + $commandID = strtoupper($commandID); + + if (!isset($this->commands[$commandID])) { + throw new ClientException("Command '$commandID' is not a registered Redis command."); + } + + $commandClass = $this->commands[$commandID]; + $command = new $commandClass(); + $command->setArguments($arguments); + + if (isset($this->processor)) { + $this->processor->process($command); + } + + return $command; + } + + /** + * Defines a new command in the server profile. + * + * @param string $commandID Command ID. + * @param string $class Fully-qualified name of a Predis\Command\CommandInterface. + * + * @throws \InvalidArgumentException + */ + public function defineCommand($commandID, $class) + { + $reflection = new \ReflectionClass($class); + + if (!$reflection->isSubclassOf('Predis\Command\CommandInterface')) { + throw new \InvalidArgumentException("The class '$class' is not a valid command class."); + } + + $this->commands[strtoupper($commandID)] = $class; + } + + /** + * {@inheritdoc} + */ + public function setProcessor(ProcessorInterface $processor = null) + { + $this->processor = $processor; + } + + /** + * {@inheritdoc} + */ + public function getProcessor() + { + return $this->processor; + } + + /** + * Returns the version of server profile as its string representation. + * + * @return string + */ + public function __toString() + { + return $this->getVersion(); + } +} diff --git a/vendor/predis/predis/src/Profile/RedisUnstable.php b/vendor/predis/predis/src/Profile/RedisUnstable.php new file mode 100644 index 0000000..573cc9e --- /dev/null +++ b/vendor/predis/predis/src/Profile/RedisUnstable.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for the current unstable version of Redis. + * + * @author Daniele Alessandri + */ +class RedisUnstable extends RedisVersion320 +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '3.2'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array_merge(parent::getSupportedCommands(), array( + // EMPTY + )); + } +} diff --git a/vendor/predis/predis/src/Profile/RedisVersion200.php b/vendor/predis/predis/src/Profile/RedisVersion200.php new file mode 100644 index 0000000..234d53c --- /dev/null +++ b/vendor/predis/predis/src/Profile/RedisVersion200.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 2.0. + * + * @author Daniele Alessandri + */ +class RedisVersion200 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '2.0'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfo', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + ); + } +} diff --git a/vendor/predis/predis/src/Profile/RedisVersion220.php b/vendor/predis/predis/src/Profile/RedisVersion220.php new file mode 100644 index 0000000..899014e --- /dev/null +++ b/vendor/predis/predis/src/Profile/RedisVersion220.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 2.2. + * + * @author Daniele Alessandri + */ +class RedisVersion220 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '2.2'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfo', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + + /* ---------------- Redis 2.2 ---------------- */ + + /* commands operating on the key space */ + 'PERSIST' => 'Predis\Command\KeyPersist', + + /* commands operating on string values */ + 'STRLEN' => 'Predis\Command\StringStrlen', + 'SETRANGE' => 'Predis\Command\StringSetRange', + 'GETRANGE' => 'Predis\Command\StringGetRange', + 'SETBIT' => 'Predis\Command\StringSetBit', + 'GETBIT' => 'Predis\Command\StringGetBit', + + /* commands operating on lists */ + 'RPUSHX' => 'Predis\Command\ListPushTailX', + 'LPUSHX' => 'Predis\Command\ListPushHeadX', + 'LINSERT' => 'Predis\Command\ListInsert', + 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', + + /* commands operating on sorted sets */ + 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', + + /* transactions */ + 'WATCH' => 'Predis\Command\TransactionWatch', + 'UNWATCH' => 'Predis\Command\TransactionUnwatch', + + /* remote server control commands */ + 'OBJECT' => 'Predis\Command\ServerObject', + 'SLOWLOG' => 'Predis\Command\ServerSlowlog', + ); + } +} diff --git a/vendor/predis/predis/src/Profile/RedisVersion240.php b/vendor/predis/predis/src/Profile/RedisVersion240.php new file mode 100644 index 0000000..0856c37 --- /dev/null +++ b/vendor/predis/predis/src/Profile/RedisVersion240.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 Predis\Profile; + +/** + * Server profile for Redis 2.4. + * + * @author Daniele Alessandri + */ +class RedisVersion240 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '2.4'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfo', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + + /* ---------------- Redis 2.2 ---------------- */ + + /* commands operating on the key space */ + 'PERSIST' => 'Predis\Command\KeyPersist', + + /* commands operating on string values */ + 'STRLEN' => 'Predis\Command\StringStrlen', + 'SETRANGE' => 'Predis\Command\StringSetRange', + 'GETRANGE' => 'Predis\Command\StringGetRange', + 'SETBIT' => 'Predis\Command\StringSetBit', + 'GETBIT' => 'Predis\Command\StringGetBit', + + /* commands operating on lists */ + 'RPUSHX' => 'Predis\Command\ListPushTailX', + 'LPUSHX' => 'Predis\Command\ListPushHeadX', + 'LINSERT' => 'Predis\Command\ListInsert', + 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', + + /* commands operating on sorted sets */ + 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', + + /* transactions */ + 'WATCH' => 'Predis\Command\TransactionWatch', + 'UNWATCH' => 'Predis\Command\TransactionUnwatch', + + /* remote server control commands */ + 'OBJECT' => 'Predis\Command\ServerObject', + 'SLOWLOG' => 'Predis\Command\ServerSlowlog', + + /* ---------------- Redis 2.4 ---------------- */ + + /* remote server control commands */ + 'CLIENT' => 'Predis\Command\ServerClient', + ); + } +} diff --git a/vendor/predis/predis/src/Profile/RedisVersion260.php b/vendor/predis/predis/src/Profile/RedisVersion260.php new file mode 100644 index 0000000..ba5084a --- /dev/null +++ b/vendor/predis/predis/src/Profile/RedisVersion260.php @@ -0,0 +1,235 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 2.6. + * + * @author Daniele Alessandri + */ +class RedisVersion260 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '2.6'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + 'DUMP' => 'Predis\Command\KeyDump', + 'RESTORE' => 'Predis\Command\KeyRestore', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfoV26x', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + + /* ---------------- Redis 2.2 ---------------- */ + + /* commands operating on the key space */ + 'PERSIST' => 'Predis\Command\KeyPersist', + + /* commands operating on string values */ + 'STRLEN' => 'Predis\Command\StringStrlen', + 'SETRANGE' => 'Predis\Command\StringSetRange', + 'GETRANGE' => 'Predis\Command\StringGetRange', + 'SETBIT' => 'Predis\Command\StringSetBit', + 'GETBIT' => 'Predis\Command\StringGetBit', + + /* commands operating on lists */ + 'RPUSHX' => 'Predis\Command\ListPushTailX', + 'LPUSHX' => 'Predis\Command\ListPushHeadX', + 'LINSERT' => 'Predis\Command\ListInsert', + 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', + + /* commands operating on sorted sets */ + 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', + + /* transactions */ + 'WATCH' => 'Predis\Command\TransactionWatch', + 'UNWATCH' => 'Predis\Command\TransactionUnwatch', + + /* remote server control commands */ + 'OBJECT' => 'Predis\Command\ServerObject', + 'SLOWLOG' => 'Predis\Command\ServerSlowlog', + + /* ---------------- Redis 2.4 ---------------- */ + + /* remote server control commands */ + 'CLIENT' => 'Predis\Command\ServerClient', + + /* ---------------- Redis 2.6 ---------------- */ + + /* commands operating on the key space */ + 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', + 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', + 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', + 'MIGRATE' => 'Predis\Command\KeyMigrate', + + /* commands operating on string values */ + 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', + 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', + 'BITOP' => 'Predis\Command\StringBitOp', + 'BITCOUNT' => 'Predis\Command\StringBitCount', + + /* commands operating on hashes */ + 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', + + /* scripting */ + 'EVAL' => 'Predis\Command\ServerEval', + 'EVALSHA' => 'Predis\Command\ServerEvalSHA', + 'SCRIPT' => 'Predis\Command\ServerScript', + + /* remote server control commands */ + 'TIME' => 'Predis\Command\ServerTime', + 'SENTINEL' => 'Predis\Command\ServerSentinel', + ); + } +} diff --git a/vendor/predis/predis/src/Profile/RedisVersion280.php b/vendor/predis/predis/src/Profile/RedisVersion280.php new file mode 100644 index 0000000..ea17e68 --- /dev/null +++ b/vendor/predis/predis/src/Profile/RedisVersion280.php @@ -0,0 +1,267 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 2.8. + * + * @author Daniele Alessandri + */ +class RedisVersion280 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '2.8'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + 'DUMP' => 'Predis\Command\KeyDump', + 'RESTORE' => 'Predis\Command\KeyRestore', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfoV26x', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + + /* ---------------- Redis 2.2 ---------------- */ + + /* commands operating on the key space */ + 'PERSIST' => 'Predis\Command\KeyPersist', + + /* commands operating on string values */ + 'STRLEN' => 'Predis\Command\StringStrlen', + 'SETRANGE' => 'Predis\Command\StringSetRange', + 'GETRANGE' => 'Predis\Command\StringGetRange', + 'SETBIT' => 'Predis\Command\StringSetBit', + 'GETBIT' => 'Predis\Command\StringGetBit', + + /* commands operating on lists */ + 'RPUSHX' => 'Predis\Command\ListPushTailX', + 'LPUSHX' => 'Predis\Command\ListPushHeadX', + 'LINSERT' => 'Predis\Command\ListInsert', + 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', + + /* commands operating on sorted sets */ + 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', + + /* transactions */ + 'WATCH' => 'Predis\Command\TransactionWatch', + 'UNWATCH' => 'Predis\Command\TransactionUnwatch', + + /* remote server control commands */ + 'OBJECT' => 'Predis\Command\ServerObject', + 'SLOWLOG' => 'Predis\Command\ServerSlowlog', + + /* ---------------- Redis 2.4 ---------------- */ + + /* remote server control commands */ + 'CLIENT' => 'Predis\Command\ServerClient', + + /* ---------------- Redis 2.6 ---------------- */ + + /* commands operating on the key space */ + 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', + 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', + 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', + 'MIGRATE' => 'Predis\Command\KeyMigrate', + + /* commands operating on string values */ + 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', + 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', + 'BITOP' => 'Predis\Command\StringBitOp', + 'BITCOUNT' => 'Predis\Command\StringBitCount', + + /* commands operating on hashes */ + 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', + + /* scripting */ + 'EVAL' => 'Predis\Command\ServerEval', + 'EVALSHA' => 'Predis\Command\ServerEvalSHA', + 'SCRIPT' => 'Predis\Command\ServerScript', + + /* remote server control commands */ + 'TIME' => 'Predis\Command\ServerTime', + 'SENTINEL' => 'Predis\Command\ServerSentinel', + + /* ---------------- Redis 2.8 ---------------- */ + + /* commands operating on the key space */ + 'SCAN' => 'Predis\Command\KeyScan', + + /* commands operating on string values */ + 'BITPOS' => 'Predis\Command\StringBitPos', + + /* commands operating on sets */ + 'SSCAN' => 'Predis\Command\SetScan', + + /* commands operating on sorted sets */ + 'ZSCAN' => 'Predis\Command\ZSetScan', + 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount', + 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex', + 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex', + 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex', + + /* commands operating on hashes */ + 'HSCAN' => 'Predis\Command\HashScan', + + /* publish - subscribe */ + 'PUBSUB' => 'Predis\Command\PubSubPubsub', + + /* commands operating on HyperLogLog */ + 'PFADD' => 'Predis\Command\HyperLogLogAdd', + 'PFCOUNT' => 'Predis\Command\HyperLogLogCount', + 'PFMERGE' => 'Predis\Command\HyperLogLogMerge', + + /* remote server control commands */ + 'COMMAND' => 'Predis\Command\ServerCommand', + ); + } +} diff --git a/vendor/predis/predis/src/Profile/RedisVersion300.php b/vendor/predis/predis/src/Profile/RedisVersion300.php new file mode 100644 index 0000000..8a2fac8 --- /dev/null +++ b/vendor/predis/predis/src/Profile/RedisVersion300.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 3.0. + * + * @author Daniele Alessandri + */ +class RedisVersion300 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '3.0'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + 'DUMP' => 'Predis\Command\KeyDump', + 'RESTORE' => 'Predis\Command\KeyRestore', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfoV26x', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + + /* ---------------- Redis 2.2 ---------------- */ + + /* commands operating on the key space */ + 'PERSIST' => 'Predis\Command\KeyPersist', + + /* commands operating on string values */ + 'STRLEN' => 'Predis\Command\StringStrlen', + 'SETRANGE' => 'Predis\Command\StringSetRange', + 'GETRANGE' => 'Predis\Command\StringGetRange', + 'SETBIT' => 'Predis\Command\StringSetBit', + 'GETBIT' => 'Predis\Command\StringGetBit', + + /* commands operating on lists */ + 'RPUSHX' => 'Predis\Command\ListPushTailX', + 'LPUSHX' => 'Predis\Command\ListPushHeadX', + 'LINSERT' => 'Predis\Command\ListInsert', + 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', + + /* commands operating on sorted sets */ + 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', + + /* transactions */ + 'WATCH' => 'Predis\Command\TransactionWatch', + 'UNWATCH' => 'Predis\Command\TransactionUnwatch', + + /* remote server control commands */ + 'OBJECT' => 'Predis\Command\ServerObject', + 'SLOWLOG' => 'Predis\Command\ServerSlowlog', + + /* ---------------- Redis 2.4 ---------------- */ + + /* remote server control commands */ + 'CLIENT' => 'Predis\Command\ServerClient', + + /* ---------------- Redis 2.6 ---------------- */ + + /* commands operating on the key space */ + 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', + 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', + 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', + 'MIGRATE' => 'Predis\Command\KeyMigrate', + + /* commands operating on string values */ + 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', + 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', + 'BITOP' => 'Predis\Command\StringBitOp', + 'BITCOUNT' => 'Predis\Command\StringBitCount', + + /* commands operating on hashes */ + 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', + + /* scripting */ + 'EVAL' => 'Predis\Command\ServerEval', + 'EVALSHA' => 'Predis\Command\ServerEvalSHA', + 'SCRIPT' => 'Predis\Command\ServerScript', + + /* remote server control commands */ + 'TIME' => 'Predis\Command\ServerTime', + 'SENTINEL' => 'Predis\Command\ServerSentinel', + + /* ---------------- Redis 2.8 ---------------- */ + + /* commands operating on the key space */ + 'SCAN' => 'Predis\Command\KeyScan', + + /* commands operating on string values */ + 'BITPOS' => 'Predis\Command\StringBitPos', + + /* commands operating on sets */ + 'SSCAN' => 'Predis\Command\SetScan', + + /* commands operating on sorted sets */ + 'ZSCAN' => 'Predis\Command\ZSetScan', + 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount', + 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex', + 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex', + 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex', + + /* commands operating on hashes */ + 'HSCAN' => 'Predis\Command\HashScan', + + /* publish - subscribe */ + 'PUBSUB' => 'Predis\Command\PubSubPubsub', + + /* commands operating on HyperLogLog */ + 'PFADD' => 'Predis\Command\HyperLogLogAdd', + 'PFCOUNT' => 'Predis\Command\HyperLogLogCount', + 'PFMERGE' => 'Predis\Command\HyperLogLogMerge', + + /* remote server control commands */ + 'COMMAND' => 'Predis\Command\ServerCommand', + + /* ---------------- Redis 3.0 ---------------- */ + + ); + } +} diff --git a/vendor/predis/predis/src/Profile/RedisVersion320.php b/vendor/predis/predis/src/Profile/RedisVersion320.php new file mode 100644 index 0000000..7de7957 --- /dev/null +++ b/vendor/predis/predis/src/Profile/RedisVersion320.php @@ -0,0 +1,281 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 3.0. + * + * @author Daniele Alessandri + */ +class RedisVersion320 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '3.2'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + 'DUMP' => 'Predis\Command\KeyDump', + 'RESTORE' => 'Predis\Command\KeyRestore', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfoV26x', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + + /* ---------------- Redis 2.2 ---------------- */ + + /* commands operating on the key space */ + 'PERSIST' => 'Predis\Command\KeyPersist', + + /* commands operating on string values */ + 'STRLEN' => 'Predis\Command\StringStrlen', + 'SETRANGE' => 'Predis\Command\StringSetRange', + 'GETRANGE' => 'Predis\Command\StringGetRange', + 'SETBIT' => 'Predis\Command\StringSetBit', + 'GETBIT' => 'Predis\Command\StringGetBit', + + /* commands operating on lists */ + 'RPUSHX' => 'Predis\Command\ListPushTailX', + 'LPUSHX' => 'Predis\Command\ListPushHeadX', + 'LINSERT' => 'Predis\Command\ListInsert', + 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', + + /* commands operating on sorted sets */ + 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', + + /* transactions */ + 'WATCH' => 'Predis\Command\TransactionWatch', + 'UNWATCH' => 'Predis\Command\TransactionUnwatch', + + /* remote server control commands */ + 'OBJECT' => 'Predis\Command\ServerObject', + 'SLOWLOG' => 'Predis\Command\ServerSlowlog', + + /* ---------------- Redis 2.4 ---------------- */ + + /* remote server control commands */ + 'CLIENT' => 'Predis\Command\ServerClient', + + /* ---------------- Redis 2.6 ---------------- */ + + /* commands operating on the key space */ + 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', + 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', + 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', + 'MIGRATE' => 'Predis\Command\KeyMigrate', + + /* commands operating on string values */ + 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', + 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', + 'BITOP' => 'Predis\Command\StringBitOp', + 'BITCOUNT' => 'Predis\Command\StringBitCount', + + /* commands operating on hashes */ + 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', + + /* scripting */ + 'EVAL' => 'Predis\Command\ServerEval', + 'EVALSHA' => 'Predis\Command\ServerEvalSHA', + 'SCRIPT' => 'Predis\Command\ServerScript', + + /* remote server control commands */ + 'TIME' => 'Predis\Command\ServerTime', + 'SENTINEL' => 'Predis\Command\ServerSentinel', + + /* ---------------- Redis 2.8 ---------------- */ + + /* commands operating on the key space */ + 'SCAN' => 'Predis\Command\KeyScan', + + /* commands operating on string values */ + 'BITPOS' => 'Predis\Command\StringBitPos', + + /* commands operating on sets */ + 'SSCAN' => 'Predis\Command\SetScan', + + /* commands operating on sorted sets */ + 'ZSCAN' => 'Predis\Command\ZSetScan', + 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount', + 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex', + 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex', + 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex', + + /* commands operating on hashes */ + 'HSCAN' => 'Predis\Command\HashScan', + + /* publish - subscribe */ + 'PUBSUB' => 'Predis\Command\PubSubPubsub', + + /* commands operating on HyperLogLog */ + 'PFADD' => 'Predis\Command\HyperLogLogAdd', + 'PFCOUNT' => 'Predis\Command\HyperLogLogCount', + 'PFMERGE' => 'Predis\Command\HyperLogLogMerge', + + /* remote server control commands */ + 'COMMAND' => 'Predis\Command\ServerCommand', + + /* ---------------- Redis 3.2 ---------------- */ + + /* commands operating on hashes */ + 'HSTRLEN' => 'Predis\Command\HashStringLength', + 'BITFIELD' => 'Predis\Command\StringBitField', + + /* commands performing geospatial operations */ + 'GEOADD' => 'Predis\Command\GeospatialGeoAdd', + 'GEOHASH' => 'Predis\Command\GeospatialGeoHash', + 'GEOPOS' => 'Predis\Command\GeospatialGeoPos', + 'GEODIST' => 'Predis\Command\GeospatialGeoDist', + 'GEORADIUS' => 'Predis\Command\GeospatialGeoRadius', + 'GEORADIUSBYMEMBER' => 'Predis\Command\GeospatialGeoRadiusByMember', + ); + } +} diff --git a/vendor/predis/predis/src/Protocol/ProtocolException.php b/vendor/predis/predis/src/Protocol/ProtocolException.php new file mode 100644 index 0000000..6fe5d6d --- /dev/null +++ b/vendor/predis/predis/src/Protocol/ProtocolException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol; + +use Predis\CommunicationException; + +/** + * Exception used to indentify errors encountered while parsing the Redis wire + * protocol. + * + * @author Daniele Alessandri + */ +class ProtocolException extends CommunicationException +{ +} diff --git a/vendor/predis/predis/src/Protocol/ProtocolProcessorInterface.php b/vendor/predis/predis/src/Protocol/ProtocolProcessorInterface.php new file mode 100644 index 0000000..b34ea18 --- /dev/null +++ b/vendor/predis/predis/src/Protocol/ProtocolProcessorInterface.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol; + +use Predis\Command\CommandInterface; +use Predis\Connection\CompositeConnectionInterface; + +/** + * Defines a pluggable protocol processor capable of serializing commands and + * deserializing responses into PHP objects directly from a connection. + * + * @author Daniele Alessandri + */ +interface ProtocolProcessorInterface +{ + /** + * Writes a request over a connection to Redis. + * + * @param CompositeConnectionInterface $connection Redis connection. + * @param CommandInterface $command Command instance. + */ + public function write(CompositeConnectionInterface $connection, CommandInterface $command); + + /** + * Reads a response from a connection to Redis. + * + * @param CompositeConnectionInterface $connection Redis connection. + * + * @return mixed + */ + public function read(CompositeConnectionInterface $connection); +} diff --git a/vendor/predis/predis/src/Protocol/RequestSerializerInterface.php b/vendor/predis/predis/src/Protocol/RequestSerializerInterface.php new file mode 100644 index 0000000..eef72a6 --- /dev/null +++ b/vendor/predis/predis/src/Protocol/RequestSerializerInterface.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 Predis\Protocol; + +use Predis\Command\CommandInterface; + +/** + * Defines a pluggable serializer for Redis commands. + * + * @author Daniele Alessandri + */ +interface RequestSerializerInterface +{ + /** + * Serializes a Redis command. + * + * @param CommandInterface $command Redis command. + * + * @return string + */ + public function serialize(CommandInterface $command); +} diff --git a/vendor/predis/predis/src/Protocol/ResponseReaderInterface.php b/vendor/predis/predis/src/Protocol/ResponseReaderInterface.php new file mode 100644 index 0000000..86a7bdc --- /dev/null +++ b/vendor/predis/predis/src/Protocol/ResponseReaderInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol; + +use Predis\Connection\CompositeConnectionInterface; + +/** + * Defines a pluggable reader capable of parsing responses returned by Redis and + * deserializing them to PHP objects. + * + * @author Daniele Alessandri + */ +interface ResponseReaderInterface +{ + /** + * Reads a response from a connection to Redis. + * + * @param CompositeConnectionInterface $connection Redis connection. + * + * @return mixed + */ + public function read(CompositeConnectionInterface $connection); +} diff --git a/vendor/predis/predis/src/Protocol/Text/CompositeProtocolProcessor.php b/vendor/predis/predis/src/Protocol/Text/CompositeProtocolProcessor.php new file mode 100644 index 0000000..ea85ed3 --- /dev/null +++ b/vendor/predis/predis/src/Protocol/Text/CompositeProtocolProcessor.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 Predis\Protocol\Text; + +use Predis\Command\CommandInterface; +use Predis\Connection\CompositeConnectionInterface; +use Predis\Protocol\ProtocolProcessorInterface; +use Predis\Protocol\RequestSerializerInterface; +use Predis\Protocol\ResponseReaderInterface; + +/** + * Composite protocol processor for the standard Redis wire protocol using + * pluggable handlers to serialize requests and deserialize responses. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class CompositeProtocolProcessor implements ProtocolProcessorInterface +{ + /* + * @var RequestSerializerInterface + */ + protected $serializer; + + /* + * @var ResponseReaderInterface + */ + protected $reader; + + /** + * @param RequestSerializerInterface $serializer Request serializer. + * @param ResponseReaderInterface $reader Response reader. + */ + public function __construct( + RequestSerializerInterface $serializer = null, + ResponseReaderInterface $reader = null + ) { + $this->setRequestSerializer($serializer ?: new RequestSerializer()); + $this->setResponseReader($reader ?: new ResponseReader()); + } + + /** + * {@inheritdoc} + */ + public function write(CompositeConnectionInterface $connection, CommandInterface $command) + { + $connection->writeBuffer($this->serializer->serialize($command)); + } + + /** + * {@inheritdoc} + */ + public function read(CompositeConnectionInterface $connection) + { + return $this->reader->read($connection); + } + + /** + * Sets the request serializer used by the protocol processor. + * + * @param RequestSerializerInterface $serializer Request serializer. + */ + public function setRequestSerializer(RequestSerializerInterface $serializer) + { + $this->serializer = $serializer; + } + + /** + * Returns the request serializer used by the protocol processor. + * + * @return RequestSerializerInterface + */ + public function getRequestSerializer() + { + return $this->serializer; + } + + /** + * Sets the response reader used by the protocol processor. + * + * @param ResponseReaderInterface $reader Response reader. + */ + public function setResponseReader(ResponseReaderInterface $reader) + { + $this->reader = $reader; + } + + /** + * Returns the Response reader used by the protocol processor. + * + * @return ResponseReaderInterface + */ + public function getResponseReader() + { + return $this->reader; + } +} diff --git a/vendor/predis/predis/src/Protocol/Text/Handler/BulkResponse.php b/vendor/predis/predis/src/Protocol/Text/Handler/BulkResponse.php new file mode 100644 index 0000000..5b0bf3c --- /dev/null +++ b/vendor/predis/predis/src/Protocol/Text/Handler/BulkResponse.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 Predis\Protocol\Text\Handler; + +use Predis\CommunicationException; +use Predis\Connection\CompositeConnectionInterface; +use Predis\Protocol\ProtocolException; + +/** + * Handler for the bulk response type in the standard Redis wire protocol. + * It translates the payload to a string or a NULL. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class BulkResponse implements ResponseHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(CompositeConnectionInterface $connection, $payload) + { + $length = (int) $payload; + + if ("$length" !== $payload) { + CommunicationException::handle(new ProtocolException( + $connection, "Cannot parse '$payload' as a valid length for a bulk response." + )); + } + + if ($length >= 0) { + return substr($connection->readBuffer($length + 2), 0, -2); + } + + if ($length == -1) { + return; + } + + CommunicationException::handle(new ProtocolException( + $connection, "Value '$payload' is not a valid length for a bulk response." + )); + + return; + } +} diff --git a/vendor/predis/predis/src/Protocol/Text/Handler/ErrorResponse.php b/vendor/predis/predis/src/Protocol/Text/Handler/ErrorResponse.php new file mode 100644 index 0000000..3e18b7b --- /dev/null +++ b/vendor/predis/predis/src/Protocol/Text/Handler/ErrorResponse.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 Predis\Protocol\Text\Handler; + +use Predis\Connection\CompositeConnectionInterface; +use Predis\Response\Error; + +/** + * Handler for the error response type in the standard Redis wire protocol. + * It translates the payload to a complex response object for Predis. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class ErrorResponse implements ResponseHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(CompositeConnectionInterface $connection, $payload) + { + return new Error($payload); + } +} diff --git a/vendor/predis/predis/src/Protocol/Text/Handler/IntegerResponse.php b/vendor/predis/predis/src/Protocol/Text/Handler/IntegerResponse.php new file mode 100644 index 0000000..f965601 --- /dev/null +++ b/vendor/predis/predis/src/Protocol/Text/Handler/IntegerResponse.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol\Text\Handler; + +use Predis\CommunicationException; +use Predis\Connection\CompositeConnectionInterface; +use Predis\Protocol\ProtocolException; + +/** + * Handler for the integer response type in the standard Redis wire protocol. + * It translates the payload an integer or NULL. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class IntegerResponse implements ResponseHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(CompositeConnectionInterface $connection, $payload) + { + if (is_numeric($payload)) { + $integer = (int) $payload; + return $integer == $payload ? $integer : $payload; + } + + if ($payload !== 'nil') { + CommunicationException::handle(new ProtocolException( + $connection, "Cannot parse '$payload' as a valid numeric response." + )); + } + + return; + } +} diff --git a/vendor/predis/predis/src/Protocol/Text/Handler/MultiBulkResponse.php b/vendor/predis/predis/src/Protocol/Text/Handler/MultiBulkResponse.php new file mode 100644 index 0000000..820b9b4 --- /dev/null +++ b/vendor/predis/predis/src/Protocol/Text/Handler/MultiBulkResponse.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 Predis\Protocol\Text\Handler; + +use Predis\CommunicationException; +use Predis\Connection\CompositeConnectionInterface; +use Predis\Protocol\ProtocolException; + +/** + * Handler for the multibulk response type in the standard Redis wire protocol. + * It returns multibulk responses as PHP arrays. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class MultiBulkResponse implements ResponseHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(CompositeConnectionInterface $connection, $payload) + { + $length = (int) $payload; + + if ("$length" !== $payload) { + CommunicationException::handle(new ProtocolException( + $connection, "Cannot parse '$payload' as a valid length of a multi-bulk response." + )); + } + + if ($length === -1) { + return; + } + + $list = array(); + + if ($length > 0) { + $handlersCache = array(); + $reader = $connection->getProtocol()->getResponseReader(); + + for ($i = 0; $i < $length; ++$i) { + $header = $connection->readLine(); + $prefix = $header[0]; + + if (isset($handlersCache[$prefix])) { + $handler = $handlersCache[$prefix]; + } else { + $handler = $reader->getHandler($prefix); + $handlersCache[$prefix] = $handler; + } + + $list[$i] = $handler->handle($connection, substr($header, 1)); + } + } + + return $list; + } +} diff --git a/vendor/predis/predis/src/Protocol/Text/Handler/ResponseHandlerInterface.php b/vendor/predis/predis/src/Protocol/Text/Handler/ResponseHandlerInterface.php new file mode 100644 index 0000000..ca08a9c --- /dev/null +++ b/vendor/predis/predis/src/Protocol/Text/Handler/ResponseHandlerInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol\Text\Handler; + +use Predis\Connection\CompositeConnectionInterface; + +/** + * Defines a pluggable handler used to parse a particular type of response. + * + * @author Daniele Alessandri + */ +interface ResponseHandlerInterface +{ + /** + * Deserializes a response returned by Redis and reads more data from the + * connection if needed. + * + * @param CompositeConnectionInterface $connection Redis connection. + * @param string $payload String payload. + * + * @return mixed + */ + public function handle(CompositeConnectionInterface $connection, $payload); +} diff --git a/vendor/predis/predis/src/Protocol/Text/Handler/StatusResponse.php b/vendor/predis/predis/src/Protocol/Text/Handler/StatusResponse.php new file mode 100644 index 0000000..7bde555 --- /dev/null +++ b/vendor/predis/predis/src/Protocol/Text/Handler/StatusResponse.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 Predis\Protocol\Text\Handler; + +use Predis\Connection\CompositeConnectionInterface; +use Predis\Response\Status; + +/** + * Handler for the status response type in the standard Redis wire protocol. It + * translates certain classes of status response to PHP objects or just returns + * the payload as a string. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class StatusResponse implements ResponseHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(CompositeConnectionInterface $connection, $payload) + { + return Status::get($payload); + } +} diff --git a/vendor/predis/predis/src/Protocol/Text/Handler/StreamableMultiBulkResponse.php b/vendor/predis/predis/src/Protocol/Text/Handler/StreamableMultiBulkResponse.php new file mode 100644 index 0000000..7cdb736 --- /dev/null +++ b/vendor/predis/predis/src/Protocol/Text/Handler/StreamableMultiBulkResponse.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 Predis\Protocol\Text\Handler; + +use Predis\CommunicationException; +use Predis\Connection\CompositeConnectionInterface; +use Predis\Protocol\ProtocolException; +use Predis\Response\Iterator\MultiBulk as MultiBulkIterator; + +/** + * Handler for the multibulk response type in the standard Redis wire protocol. + * It returns multibulk responses as iterators that can stream bulk elements. + * + * Streamable multibulk responses are not globally supported by the abstractions + * built-in into Predis, such as transactions or pipelines. Use them with care! + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class StreamableMultiBulkResponse implements ResponseHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(CompositeConnectionInterface $connection, $payload) + { + $length = (int) $payload; + + if ("$length" != $payload) { + CommunicationException::handle(new ProtocolException( + $connection, "Cannot parse '$payload' as a valid length for a multi-bulk response." + )); + } + + return new MultiBulkIterator($connection, $length); + } +} diff --git a/vendor/predis/predis/src/Protocol/Text/ProtocolProcessor.php b/vendor/predis/predis/src/Protocol/Text/ProtocolProcessor.php new file mode 100644 index 0000000..99acdf8 --- /dev/null +++ b/vendor/predis/predis/src/Protocol/Text/ProtocolProcessor.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol\Text; + +use Predis\Command\CommandInterface; +use Predis\CommunicationException; +use Predis\Connection\CompositeConnectionInterface; +use Predis\Protocol\ProtocolException; +use Predis\Protocol\ProtocolProcessorInterface; +use Predis\Response\Error as ErrorResponse; +use Predis\Response\Iterator\MultiBulk as MultiBulkIterator; +use Predis\Response\Status as StatusResponse; + +/** + * Protocol processor for the standard Redis wire protocol. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class ProtocolProcessor implements ProtocolProcessorInterface +{ + protected $mbiterable; + protected $serializer; + + /** + * + */ + public function __construct() + { + $this->mbiterable = false; + $this->serializer = new RequestSerializer(); + } + + /** + * {@inheritdoc} + */ + public function write(CompositeConnectionInterface $connection, CommandInterface $command) + { + $request = $this->serializer->serialize($command); + $connection->writeBuffer($request); + } + + /** + * {@inheritdoc} + */ + public function read(CompositeConnectionInterface $connection) + { + $chunk = $connection->readLine(); + $prefix = $chunk[0]; + $payload = substr($chunk, 1); + + switch ($prefix) { + case '+': + return new StatusResponse($payload); + + case '$': + $size = (int) $payload; + if ($size === -1) { + return; + } + + return substr($connection->readBuffer($size + 2), 0, -2); + + case '*': + $count = (int) $payload; + + if ($count === -1) { + return; + } + if ($this->mbiterable) { + return new MultiBulkIterator($connection, $count); + } + + $multibulk = array(); + + for ($i = 0; $i < $count; ++$i) { + $multibulk[$i] = $this->read($connection); + } + + return $multibulk; + + case ':': + $integer = (int) $payload; + return $integer == $payload ? $integer : $payload; + + case '-': + return new ErrorResponse($payload); + + default: + CommunicationException::handle(new ProtocolException( + $connection, "Unknown response prefix: '$prefix'." + )); + + return; + } + } + + /** + * Enables or disables returning multibulk responses as specialized PHP + * iterators used to stream bulk elements of a multibulk response instead + * returning a plain array. + * + * Streamable multibulk responses are not globally supported by the + * abstractions built-in into Predis, such as transactions or pipelines. + * Use them with care! + * + * @param bool $value Enable or disable streamable multibulk responses. + */ + public function useIterableMultibulk($value) + { + $this->mbiterable = (bool) $value; + } +} diff --git a/vendor/predis/predis/src/Protocol/Text/RequestSerializer.php b/vendor/predis/predis/src/Protocol/Text/RequestSerializer.php new file mode 100644 index 0000000..859595b --- /dev/null +++ b/vendor/predis/predis/src/Protocol/Text/RequestSerializer.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol\Text; + +use Predis\Command\CommandInterface; +use Predis\Protocol\RequestSerializerInterface; + +/** + * Request serializer for the standard Redis wire protocol. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class RequestSerializer implements RequestSerializerInterface +{ + /** + * {@inheritdoc} + */ + public function serialize(CommandInterface $command) + { + $commandID = $command->getId(); + $arguments = $command->getArguments(); + + $cmdlen = strlen($commandID); + $reqlen = count($arguments) + 1; + + $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n"; + + foreach ($arguments as $argument) { + $arglen = strlen($argument); + $buffer .= "\${$arglen}\r\n{$argument}\r\n"; + } + + return $buffer; + } +} diff --git a/vendor/predis/predis/src/Protocol/Text/ResponseReader.php b/vendor/predis/predis/src/Protocol/Text/ResponseReader.php new file mode 100644 index 0000000..d96218d --- /dev/null +++ b/vendor/predis/predis/src/Protocol/Text/ResponseReader.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 Predis\Protocol\Text; + +use Predis\CommunicationException; +use Predis\Connection\CompositeConnectionInterface; +use Predis\Protocol\ProtocolException; +use Predis\Protocol\ResponseReaderInterface; + +/** + * Response reader for the standard Redis wire protocol. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class ResponseReader implements ResponseReaderInterface +{ + protected $handlers; + + /** + * + */ + public function __construct() + { + $this->handlers = $this->getDefaultHandlers(); + } + + /** + * Returns the default handlers for the supported type of responses. + * + * @return array + */ + protected function getDefaultHandlers() + { + return array( + '+' => new Handler\StatusResponse(), + '-' => new Handler\ErrorResponse(), + ':' => new Handler\IntegerResponse(), + '$' => new Handler\BulkResponse(), + '*' => new Handler\MultiBulkResponse(), + ); + } + + /** + * Sets the handler for the specified prefix identifying the response type. + * + * @param string $prefix Identifier of the type of response. + * @param Handler\ResponseHandlerInterface $handler Response handler. + */ + public function setHandler($prefix, Handler\ResponseHandlerInterface $handler) + { + $this->handlers[$prefix] = $handler; + } + + /** + * Returns the response handler associated to a certain type of response. + * + * @param string $prefix Identifier of the type of response. + * + * @return Handler\ResponseHandlerInterface + */ + public function getHandler($prefix) + { + if (isset($this->handlers[$prefix])) { + return $this->handlers[$prefix]; + } + + return; + } + + /** + * {@inheritdoc} + */ + public function read(CompositeConnectionInterface $connection) + { + $header = $connection->readLine(); + + if ($header === '') { + $this->onProtocolError($connection, 'Unexpected empty reponse header.'); + } + + $prefix = $header[0]; + + if (!isset($this->handlers[$prefix])) { + $this->onProtocolError($connection, "Unknown response prefix: '$prefix'."); + } + + $payload = $this->handlers[$prefix]->handle($connection, substr($header, 1)); + + return $payload; + } + + /** + * Handles protocol errors generated while reading responses from a + * connection. + * + * @param CompositeConnectionInterface $connection Redis connection that generated the error. + * @param string $message Error message. + */ + protected function onProtocolError(CompositeConnectionInterface $connection, $message) + { + CommunicationException::handle( + new ProtocolException($connection, $message) + ); + } +} diff --git a/vendor/predis/predis/src/PubSub/AbstractConsumer.php b/vendor/predis/predis/src/PubSub/AbstractConsumer.php new file mode 100644 index 0000000..8c6a71d --- /dev/null +++ b/vendor/predis/predis/src/PubSub/AbstractConsumer.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\PubSub; + +/** + * Base implementation of a PUB/SUB consumer abstraction based on PHP iterators. + * + * @author Daniele Alessandri + */ +abstract class AbstractConsumer implements \Iterator +{ + const SUBSCRIBE = 'subscribe'; + const UNSUBSCRIBE = 'unsubscribe'; + const PSUBSCRIBE = 'psubscribe'; + const PUNSUBSCRIBE = 'punsubscribe'; + const MESSAGE = 'message'; + const PMESSAGE = 'pmessage'; + const PONG = 'pong'; + + const STATUS_VALID = 1; // 0b0001 + const STATUS_SUBSCRIBED = 2; // 0b0010 + const STATUS_PSUBSCRIBED = 4; // 0b0100 + + private $position = null; + private $statusFlags = self::STATUS_VALID; + + /** + * Automatically stops the consumer when the garbage collector kicks in. + */ + public function __destruct() + { + $this->stop(true); + } + + /** + * Checks if the specified flag is valid based on the state of the consumer. + * + * @param int $value Flag. + * + * @return bool + */ + protected function isFlagSet($value) + { + return ($this->statusFlags & $value) === $value; + } + + /** + * Subscribes to the specified channels. + * + * @param mixed $channel,... One or more channel names. + */ + public function subscribe($channel /*, ... */) + { + $this->writeRequest(self::SUBSCRIBE, func_get_args()); + $this->statusFlags |= self::STATUS_SUBSCRIBED; + } + + /** + * Unsubscribes from the specified channels. + * + * @param string ... One or more channel names. + */ + public function unsubscribe(/* ... */) + { + $this->writeRequest(self::UNSUBSCRIBE, func_get_args()); + } + + /** + * Subscribes to the specified channels using a pattern. + * + * @param mixed $pattern,... One or more channel name patterns. + */ + public function psubscribe($pattern /* ... */) + { + $this->writeRequest(self::PSUBSCRIBE, func_get_args()); + $this->statusFlags |= self::STATUS_PSUBSCRIBED; + } + + /** + * Unsubscribes from the specified channels using a pattern. + * + * @param string ... One or more channel name patterns. + */ + public function punsubscribe(/* ... */) + { + $this->writeRequest(self::PUNSUBSCRIBE, func_get_args()); + } + + /** + * PING the server with an optional payload that will be echoed as a + * PONG message in the pub/sub loop. + * + * @param string $payload Optional PING payload. + */ + public function ping($payload = null) + { + $this->writeRequest('PING', array($payload)); + } + + /** + * Closes the context by unsubscribing from all the subscribed channels. The + * context can be forcefully closed by dropping the underlying connection. + * + * @param bool $drop Indicates if the context should be closed by dropping the connection. + * + * @return bool Returns false when there are no pending messages. + */ + public function stop($drop = false) + { + if (!$this->valid()) { + return false; + } + + if ($drop) { + $this->invalidate(); + $this->disconnect(); + } else { + if ($this->isFlagSet(self::STATUS_SUBSCRIBED)) { + $this->unsubscribe(); + } + if ($this->isFlagSet(self::STATUS_PSUBSCRIBED)) { + $this->punsubscribe(); + } + } + + return !$drop; + } + + /** + * Closes the underlying connection when forcing a disconnection. + */ + abstract protected function disconnect(); + + /** + * Writes a Redis command on the underlying connection. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + */ + abstract protected function writeRequest($method, $arguments); + + /** + * {@inheritdoc} + */ + public function rewind() + { + // NOOP + } + + /** + * Returns the last message payload retrieved from the server and generated + * by one of the active subscriptions. + * + * @return array + */ + public function current() + { + return $this->getValue(); + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + public function next() + { + if ($this->valid()) { + ++$this->position; + } + + return $this->position; + } + + /** + * Checks if the the consumer is still in a valid state to continue. + * + * @return bool + */ + public function valid() + { + $isValid = $this->isFlagSet(self::STATUS_VALID); + $subscriptionFlags = self::STATUS_SUBSCRIBED | self::STATUS_PSUBSCRIBED; + $hasSubscriptions = ($this->statusFlags & $subscriptionFlags) > 0; + + return $isValid && $hasSubscriptions; + } + + /** + * Resets the state of the consumer. + */ + protected function invalidate() + { + $this->statusFlags = 0; // 0b0000; + } + + /** + * Waits for a new message from the server generated by one of the active + * subscriptions and returns it when available. + * + * @return array + */ + abstract protected function getValue(); +} diff --git a/vendor/predis/predis/src/PubSub/Consumer.php b/vendor/predis/predis/src/PubSub/Consumer.php new file mode 100644 index 0000000..5f2d8a8 --- /dev/null +++ b/vendor/predis/predis/src/PubSub/Consumer.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\PubSub; + +use Predis\ClientException; +use Predis\ClientInterface; +use Predis\Command\Command; +use Predis\Connection\AggregateConnectionInterface; +use Predis\NotSupportedException; + +/** + * PUB/SUB consumer abstraction. + * + * @author Daniele Alessandri + */ +class Consumer extends AbstractConsumer +{ + private $client; + private $options; + + /** + * @param ClientInterface $client Client instance used by the consumer. + * @param array $options Options for the consumer initialization. + */ + public function __construct(ClientInterface $client, array $options = null) + { + $this->checkCapabilities($client); + + $this->options = $options ?: array(); + $this->client = $client; + + $this->genericSubscribeInit('subscribe'); + $this->genericSubscribeInit('psubscribe'); + } + + /** + * Returns the underlying client instance used by the pub/sub iterator. + * + * @return ClientInterface + */ + public function getClient() + { + return $this->client; + } + + /** + * Checks if the client instance satisfies the required conditions needed to + * initialize a PUB/SUB consumer. + * + * @param ClientInterface $client Client instance used by the consumer. + * + * @throws NotSupportedException + */ + private function checkCapabilities(ClientInterface $client) + { + if ($client->getConnection() instanceof AggregateConnectionInterface) { + throw new NotSupportedException( + 'Cannot initialize a PUB/SUB consumer over aggregate connections.' + ); + } + + $commands = array('publish', 'subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe'); + + if ($client->getProfile()->supportsCommands($commands) === false) { + throw new NotSupportedException( + 'The current profile does not support PUB/SUB related commands.' + ); + } + } + + /** + * This method shares the logic to handle both SUBSCRIBE and PSUBSCRIBE. + * + * @param string $subscribeAction Type of subscription. + */ + private function genericSubscribeInit($subscribeAction) + { + if (isset($this->options[$subscribeAction])) { + $this->$subscribeAction($this->options[$subscribeAction]); + } + } + + /** + * {@inheritdoc} + */ + protected function writeRequest($method, $arguments) + { + $this->client->getConnection()->writeRequest( + $this->client->createCommand($method, + Command::normalizeArguments($arguments) + ) + ); + } + + /** + * {@inheritdoc} + */ + protected function disconnect() + { + $this->client->disconnect(); + } + + /** + * {@inheritdoc} + */ + protected function getValue() + { + $response = $this->client->getConnection()->read(); + + switch ($response[0]) { + case self::SUBSCRIBE: + case self::UNSUBSCRIBE: + case self::PSUBSCRIBE: + case self::PUNSUBSCRIBE: + if ($response[2] === 0) { + $this->invalidate(); + } + // The missing break here is intentional as we must process + // subscriptions and unsubscriptions as standard messages. + // no break + + case self::MESSAGE: + return (object) array( + 'kind' => $response[0], + 'channel' => $response[1], + 'payload' => $response[2], + ); + + case self::PMESSAGE: + return (object) array( + 'kind' => $response[0], + 'pattern' => $response[1], + 'channel' => $response[2], + 'payload' => $response[3], + ); + + case self::PONG: + return (object) array( + 'kind' => $response[0], + 'payload' => $response[1], + ); + + default: + throw new ClientException( + "Unknown message type '{$response[0]}' received in the PUB/SUB context." + ); + } + } +} diff --git a/vendor/predis/predis/src/PubSub/DispatcherLoop.php b/vendor/predis/predis/src/PubSub/DispatcherLoop.php new file mode 100644 index 0000000..d0369e7 --- /dev/null +++ b/vendor/predis/predis/src/PubSub/DispatcherLoop.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\PubSub; + +/** + * Method-dispatcher loop built around the client-side abstraction of a Redis + * PUB / SUB context. + * + * @author Daniele Alessandri + */ +class DispatcherLoop +{ + private $pubsub; + + protected $callbacks; + protected $defaultCallback; + protected $subscriptionCallback; + + /** + * @param Consumer $pubsub PubSub consumer instance used by the loop. + */ + public function __construct(Consumer $pubsub) + { + $this->callbacks = array(); + $this->pubsub = $pubsub; + } + + /** + * Checks if the passed argument is a valid callback. + * + * @param mixed $callable A callback. + * + * @throws \InvalidArgumentException + */ + protected function assertCallback($callable) + { + if (!is_callable($callable)) { + throw new \InvalidArgumentException('The given argument must be a callable object.'); + } + } + + /** + * Returns the underlying PUB / SUB context. + * + * @return Consumer + */ + public function getPubSubConsumer() + { + return $this->pubsub; + } + + /** + * Sets a callback that gets invoked upon new subscriptions. + * + * @param mixed $callable A callback. + */ + public function subscriptionCallback($callable = null) + { + if (isset($callable)) { + $this->assertCallback($callable); + } + + $this->subscriptionCallback = $callable; + } + + /** + * Sets a callback that gets invoked when a message is received on a + * channel that does not have an associated callback. + * + * @param mixed $callable A callback. + */ + public function defaultCallback($callable = null) + { + if (isset($callable)) { + $this->assertCallback($callable); + } + + $this->subscriptionCallback = $callable; + } + + /** + * Binds a callback to a channel. + * + * @param string $channel Channel name. + * @param callable $callback A callback. + */ + public function attachCallback($channel, $callback) + { + $callbackName = $this->getPrefixKeys().$channel; + + $this->assertCallback($callback); + $this->callbacks[$callbackName] = $callback; + $this->pubsub->subscribe($channel); + } + + /** + * Stops listening to a channel and removes the associated callback. + * + * @param string $channel Redis channel. + */ + public function detachCallback($channel) + { + $callbackName = $this->getPrefixKeys().$channel; + + if (isset($this->callbacks[$callbackName])) { + unset($this->callbacks[$callbackName]); + $this->pubsub->unsubscribe($channel); + } + } + + /** + * Starts the dispatcher loop. + */ + public function run() + { + foreach ($this->pubsub as $message) { + $kind = $message->kind; + + if ($kind !== Consumer::MESSAGE && $kind !== Consumer::PMESSAGE) { + if (isset($this->subscriptionCallback)) { + $callback = $this->subscriptionCallback; + call_user_func($callback, $message); + } + + continue; + } + + if (isset($this->callbacks[$message->channel])) { + $callback = $this->callbacks[$message->channel]; + call_user_func($callback, $message->payload); + } elseif (isset($this->defaultCallback)) { + $callback = $this->defaultCallback; + call_user_func($callback, $message); + } + } + } + + /** + * Terminates the dispatcher loop. + */ + public function stop() + { + $this->pubsub->stop(); + } + + /** + * Return the prefix used for keys. + * + * @return string + */ + protected function getPrefixKeys() + { + $options = $this->pubsub->getClient()->getOptions(); + + if (isset($options->prefix)) { + return $options->prefix->getPrefix(); + } + + return ''; + } +} diff --git a/vendor/predis/predis/src/Replication/MissingMasterException.php b/vendor/predis/predis/src/Replication/MissingMasterException.php new file mode 100644 index 0000000..223bd2d --- /dev/null +++ b/vendor/predis/predis/src/Replication/MissingMasterException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Replication; + +use Predis\ClientException; + +/** + * Exception class that identifies when master is missing in a replication setup. + * + * @author Daniele Alessandri + */ +class MissingMasterException extends ClientException +{ +} diff --git a/vendor/predis/predis/src/Replication/ReplicationStrategy.php b/vendor/predis/predis/src/Replication/ReplicationStrategy.php new file mode 100644 index 0000000..cd2d0ed --- /dev/null +++ b/vendor/predis/predis/src/Replication/ReplicationStrategy.php @@ -0,0 +1,304 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Replication; + +use Predis\Command\CommandInterface; +use Predis\NotSupportedException; + +/** + * Defines a strategy for master/slave replication. + * + * @author Daniele Alessandri + */ +class ReplicationStrategy +{ + protected $disallowed; + protected $readonly; + protected $readonlySHA1; + + /** + * + */ + public function __construct() + { + $this->disallowed = $this->getDisallowedOperations(); + $this->readonly = $this->getReadOnlyOperations(); + $this->readonlySHA1 = array(); + } + + /** + * Returns if the specified command will perform a read-only operation + * on Redis or not. + * + * @param CommandInterface $command Command instance. + * + * @throws NotSupportedException + * + * @return bool + */ + public function isReadOperation(CommandInterface $command) + { + if (isset($this->disallowed[$id = $command->getId()])) { + throw new NotSupportedException( + "The command '$id' is not allowed in replication mode." + ); + } + + if (isset($this->readonly[$id])) { + if (true === $readonly = $this->readonly[$id]) { + return true; + } + + return call_user_func($readonly, $command); + } + + if (($eval = $id === 'EVAL') || $id === 'EVALSHA') { + $sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0); + + if (isset($this->readonlySHA1[$sha1])) { + if (true === $readonly = $this->readonlySHA1[$sha1]) { + return true; + } + + return call_user_func($readonly, $command); + } + } + + return false; + } + + /** + * Returns if the specified command is not allowed for execution in a master + * / slave replication context. + * + * @param CommandInterface $command Command instance. + * + * @return bool + */ + public function isDisallowedOperation(CommandInterface $command) + { + return isset($this->disallowed[$command->getId()]); + } + + /** + * Checks if a SORT command is a readable operation by parsing the arguments + * array of the specified commad instance. + * + * @param CommandInterface $command Command instance. + * + * @return bool + */ + protected function isSortReadOnly(CommandInterface $command) + { + $arguments = $command->getArguments(); + $argc = count($arguments); + + if ($argc > 1) { + for ($i = 1; $i < $argc; ++$i) { + $argument = strtoupper($arguments[$i]); + if ($argument === 'STORE') { + return false; + } + } + } + + return true; + } + + /** + * Checks if BITFIELD performs a read-only operation by looking for certain + * SET and INCRYBY modifiers in the arguments array of the command. + * + * @param CommandInterface $command Command instance. + * + * @return bool + */ + protected function isBitfieldReadOnly(CommandInterface $command) + { + $arguments = $command->getArguments(); + $argc = count($arguments); + + if ($argc >= 2) { + for ($i = 1; $i < $argc; ++$i) { + $argument = strtoupper($arguments[$i]); + if ($argument === 'SET' || $argument === 'INCRBY') { + return false; + } + } + } + + return true; + } + + /** + * Checks if a GEORADIUS command is a readable operation by parsing the + * arguments array of the specified commad instance. + * + * @param CommandInterface $command Command instance. + * + * @return bool + */ + protected function isGeoradiusReadOnly(CommandInterface $command) + { + $arguments = $command->getArguments(); + $argc = count($arguments); + $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; + + if ($argc > $startIndex) { + for ($i = $startIndex; $i < $argc; ++$i) { + $argument = strtoupper($arguments[$i]); + if ($argument === 'STORE' || $argument === 'STOREDIST') { + return false; + } + } + } + + return true; + } + + /** + * Marks a command as a read-only operation. + * + * When the behavior of a command can be decided only at runtime depending + * on its arguments, a callable object can be provided to dynamically check + * if the specified command performs a read or a write operation. + * + * @param string $commandID Command ID. + * @param mixed $readonly A boolean value or a callable object. + */ + public function setCommandReadOnly($commandID, $readonly = true) + { + $commandID = strtoupper($commandID); + + if ($readonly) { + $this->readonly[$commandID] = $readonly; + } else { + unset($this->readonly[$commandID]); + } + } + + /** + * Marks a Lua script for EVAL and EVALSHA as a read-only operation. When + * the behaviour of a script can be decided only at runtime depending on + * its arguments, a callable object can be provided to dynamically check + * if the passed instance of EVAL or EVALSHA performs write operations or + * not. + * + * @param string $script Body of the Lua script. + * @param mixed $readonly A boolean value or a callable object. + */ + public function setScriptReadOnly($script, $readonly = true) + { + $sha1 = sha1($script); + + if ($readonly) { + $this->readonlySHA1[$sha1] = $readonly; + } else { + unset($this->readonlySHA1[$sha1]); + } + } + + /** + * Returns the default list of disallowed commands. + * + * @return array + */ + protected function getDisallowedOperations() + { + return array( + 'SHUTDOWN' => true, + 'INFO' => true, + 'DBSIZE' => true, + 'LASTSAVE' => true, + 'CONFIG' => true, + 'MONITOR' => true, + 'SLAVEOF' => true, + 'SAVE' => true, + 'BGSAVE' => true, + 'BGREWRITEAOF' => true, + 'SLOWLOG' => true, + ); + } + + /** + * Returns the default list of commands performing read-only operations. + * + * @return array + */ + protected function getReadOnlyOperations() + { + return array( + 'EXISTS' => true, + 'TYPE' => true, + 'KEYS' => true, + 'SCAN' => true, + 'RANDOMKEY' => true, + 'TTL' => true, + 'GET' => true, + 'MGET' => true, + 'SUBSTR' => true, + 'STRLEN' => true, + 'GETRANGE' => true, + 'GETBIT' => true, + 'LLEN' => true, + 'LRANGE' => true, + 'LINDEX' => true, + 'SCARD' => true, + 'SISMEMBER' => true, + 'SINTER' => true, + 'SUNION' => true, + 'SDIFF' => true, + 'SMEMBERS' => true, + 'SSCAN' => true, + 'SRANDMEMBER' => true, + 'ZRANGE' => true, + 'ZREVRANGE' => true, + 'ZRANGEBYSCORE' => true, + 'ZREVRANGEBYSCORE' => true, + 'ZCARD' => true, + 'ZSCORE' => true, + 'ZCOUNT' => true, + 'ZRANK' => true, + 'ZREVRANK' => true, + 'ZSCAN' => true, + 'ZLEXCOUNT' => true, + 'ZRANGEBYLEX' => true, + 'ZREVRANGEBYLEX' => true, + 'HGET' => true, + 'HMGET' => true, + 'HEXISTS' => true, + 'HLEN' => true, + 'HKEYS' => true, + 'HVALS' => true, + 'HGETALL' => true, + 'HSCAN' => true, + 'HSTRLEN' => true, + 'PING' => true, + 'AUTH' => true, + 'SELECT' => true, + 'ECHO' => true, + 'QUIT' => true, + 'OBJECT' => true, + 'BITCOUNT' => true, + 'BITPOS' => true, + 'TIME' => true, + 'PFCOUNT' => true, + 'SORT' => array($this, 'isSortReadOnly'), + 'BITFIELD' => array($this, 'isBitfieldReadOnly'), + 'GEOHASH' => true, + 'GEOPOS' => true, + 'GEODIST' => true, + 'GEORADIUS' => array($this, 'isGeoradiusReadOnly'), + 'GEORADIUSBYMEMBER' => array($this, 'isGeoradiusReadOnly'), + ); + } +} diff --git a/vendor/predis/predis/src/Replication/RoleException.php b/vendor/predis/predis/src/Replication/RoleException.php new file mode 100644 index 0000000..0d9954b --- /dev/null +++ b/vendor/predis/predis/src/Replication/RoleException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Replication; + +use Predis\CommunicationException; + +/** + * Exception class that identifies a role mismatch when connecting to node + * managed by redis-sentinel. + * + * @author Daniele Alessandri + */ +class RoleException extends CommunicationException +{ +} diff --git a/vendor/predis/predis/src/Response/Error.php b/vendor/predis/predis/src/Response/Error.php new file mode 100644 index 0000000..3933857 --- /dev/null +++ b/vendor/predis/predis/src/Response/Error.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 Predis\Response; + +/** + * Represents an error returned by Redis (-ERR responses) during the execution + * of a command on the server. + * + * @author Daniele Alessandri + */ +class Error implements ErrorInterface +{ + private $message; + + /** + * @param string $message Error message returned by Redis + */ + public function __construct($message) + { + $this->message = $message; + } + + /** + * {@inheritdoc} + */ + public function getMessage() + { + return $this->message; + } + + /** + * {@inheritdoc} + */ + public function getErrorType() + { + list($errorType) = explode(' ', $this->getMessage(), 2); + + return $errorType; + } + + /** + * Converts the object to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->getMessage(); + } +} diff --git a/vendor/predis/predis/src/Response/ErrorInterface.php b/vendor/predis/predis/src/Response/ErrorInterface.php new file mode 100644 index 0000000..a4a4a02 --- /dev/null +++ b/vendor/predis/predis/src/Response/ErrorInterface.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 Predis\Response; + +/** + * Represents an error returned by Redis (responses identified by "-" in the + * Redis protocol) during the execution of an operation on the server. + * + * @author Daniele Alessandri + */ +interface ErrorInterface extends ResponseInterface +{ + /** + * Returns the error message. + * + * @return string + */ + public function getMessage(); + + /** + * Returns the error type (e.g. ERR, ASK, MOVED). + * + * @return string + */ + public function getErrorType(); +} diff --git a/vendor/predis/predis/src/Response/Iterator/MultiBulk.php b/vendor/predis/predis/src/Response/Iterator/MultiBulk.php new file mode 100644 index 0000000..b1d2924 --- /dev/null +++ b/vendor/predis/predis/src/Response/Iterator/MultiBulk.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Response\Iterator; + +use Predis\Connection\NodeConnectionInterface; + +/** + * Streamable multibulk response. + * + * @author Daniele Alessandri + */ +class MultiBulk extends MultiBulkIterator +{ + private $connection; + + /** + * @param NodeConnectionInterface $connection Connection to Redis. + * @param int $size Number of elements of the multibulk response. + */ + public function __construct(NodeConnectionInterface $connection, $size) + { + $this->connection = $connection; + $this->size = $size; + $this->position = 0; + $this->current = $size > 0 ? $this->getValue() : null; + } + + /** + * Handles the synchronization of the client with the Redis protocol when + * the garbage collector kicks in (e.g. when the iterator goes out of the + * scope of a foreach or it is unset). + */ + public function __destruct() + { + $this->drop(true); + } + + /** + * Drop queued elements that have not been read from the connection either + * by consuming the rest of the multibulk response or quickly by closing the + * underlying connection. + * + * @param bool $disconnect Consume the iterator or drop the connection. + */ + public function drop($disconnect = false) + { + if ($disconnect) { + if ($this->valid()) { + $this->position = $this->size; + $this->connection->disconnect(); + } + } else { + while ($this->valid()) { + $this->next(); + } + } + } + + /** + * Reads the next item of the multibulk response from the connection. + * + * @return mixed + */ + protected function getValue() + { + return $this->connection->read(); + } +} diff --git a/vendor/predis/predis/src/Response/Iterator/MultiBulkIterator.php b/vendor/predis/predis/src/Response/Iterator/MultiBulkIterator.php new file mode 100644 index 0000000..5d32886 --- /dev/null +++ b/vendor/predis/predis/src/Response/Iterator/MultiBulkIterator.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Response\Iterator; + +use Predis\Response\ResponseInterface; + +/** + * Iterator that abstracts the access to multibulk responses allowing them to be + * consumed in a streamable fashion without keeping the whole payload in memory. + * + * This iterator does not support rewinding which means that the iteration, once + * consumed, cannot be restarted. + * + * Always make sure that the whole iteration is consumed (or dropped) to prevent + * protocol desynchronization issues. + * + * @author Daniele Alessandri + */ +abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInterface +{ + protected $current; + protected $position; + protected $size; + + /** + * {@inheritdoc} + */ + public function rewind() + { + // NOOP + } + + /** + * {@inheritdoc} + */ + public function current() + { + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + public function next() + { + if (++$this->position < $this->size) { + $this->current = $this->getValue(); + } + } + + /** + * {@inheritdoc} + */ + public function valid() + { + return $this->position < $this->size; + } + + /** + * Returns the number of items comprising the whole multibulk response. + * + * This method should be used instead of iterator_count() to get the size of + * the current multibulk response since the former consumes the iteration to + * count the number of elements, but our iterators do not support rewinding. + * + * @return int + */ + public function count() + { + return $this->size; + } + + /** + * Returns the current position of the iterator. + * + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + abstract protected function getValue(); +} diff --git a/vendor/predis/predis/src/Response/Iterator/MultiBulkTuple.php b/vendor/predis/predis/src/Response/Iterator/MultiBulkTuple.php new file mode 100644 index 0000000..2b6f593 --- /dev/null +++ b/vendor/predis/predis/src/Response/Iterator/MultiBulkTuple.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 Predis\Response\Iterator; + +/** + * Outer iterator consuming streamable multibulk responses by yielding tuples of + * keys and values. + * + * This wrapper is useful for responses to commands such as `HGETALL` that can + * be iterater as $key => $value pairs. + * + * @author Daniele Alessandri + */ +class MultiBulkTuple extends MultiBulk implements \OuterIterator +{ + private $iterator; + + /** + * @param MultiBulk $iterator Inner multibulk response iterator. + */ + public function __construct(MultiBulk $iterator) + { + $this->checkPreconditions($iterator); + + $this->size = count($iterator) / 2; + $this->iterator = $iterator; + $this->position = $iterator->getPosition(); + $this->current = $this->size > 0 ? $this->getValue() : null; + } + + /** + * Checks for valid preconditions. + * + * @param MultiBulk $iterator Inner multibulk response iterator. + * + * @throws \InvalidArgumentException + * @throws \UnexpectedValueException + */ + protected function checkPreconditions(MultiBulk $iterator) + { + if ($iterator->getPosition() !== 0) { + throw new \InvalidArgumentException( + 'Cannot initialize a tuple iterator using an already initiated iterator.' + ); + } + + if (($size = count($iterator)) % 2 !== 0) { + throw new \UnexpectedValueException('Invalid response size for a tuple iterator.'); + } + } + + /** + * {@inheritdoc} + */ + public function getInnerIterator() + { + return $this->iterator; + } + + /** + * {@inheritdoc} + */ + public function __destruct() + { + $this->iterator->drop(true); + } + + /** + * {@inheritdoc} + */ + protected function getValue() + { + $k = $this->iterator->current(); + $this->iterator->next(); + + $v = $this->iterator->current(); + $this->iterator->next(); + + return array($k, $v); + } +} diff --git a/vendor/predis/predis/src/Response/ResponseInterface.php b/vendor/predis/predis/src/Response/ResponseInterface.php new file mode 100644 index 0000000..0af1357 --- /dev/null +++ b/vendor/predis/predis/src/Response/ResponseInterface.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 Predis\Response; + +/** + * Represents a complex response object from Redis. + * + * @author Daniele Alessandri + */ +interface ResponseInterface +{ +} diff --git a/vendor/predis/predis/src/Response/ServerException.php b/vendor/predis/predis/src/Response/ServerException.php new file mode 100644 index 0000000..407dc5b --- /dev/null +++ b/vendor/predis/predis/src/Response/ServerException.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 Predis\Response; + +use Predis\PredisException; + +/** + * Exception class that identifies server-side Redis errors. + * + * @author Daniele Alessandri + */ +class ServerException extends PredisException implements ErrorInterface +{ + /** + * Gets the type of the error returned by Redis. + * + * @return string + */ + public function getErrorType() + { + list($errorType) = explode(' ', $this->getMessage(), 2); + + return $errorType; + } + + /** + * Converts the exception to an instance of Predis\Response\Error. + * + * @return Error + */ + public function toErrorResponse() + { + return new Error($this->getMessage()); + } +} diff --git a/vendor/predis/predis/src/Response/Status.php b/vendor/predis/predis/src/Response/Status.php new file mode 100644 index 0000000..729bb66 --- /dev/null +++ b/vendor/predis/predis/src/Response/Status.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Response; + +/** + * Represents a status response returned by Redis. + * + * @author Daniele Alessandri + */ +class Status implements ResponseInterface +{ + private static $OK; + private static $QUEUED; + + private $payload; + + /** + * @param string $payload Payload of the status response as returned by Redis. + */ + public function __construct($payload) + { + $this->payload = $payload; + } + + /** + * Converts the response object to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->payload; + } + + /** + * Returns the payload of status response. + * + * @return string + */ + public function getPayload() + { + return $this->payload; + } + + /** + * Returns an instance of a status response object. + * + * Common status responses such as OK or QUEUED are cached in order to lower + * the global memory usage especially when using pipelines. + * + * @param string $payload Status response payload. + * + * @return string + */ + public static function get($payload) + { + switch ($payload) { + case 'OK': + case 'QUEUED': + if (isset(self::$$payload)) { + return self::$$payload; + } + + return self::$$payload = new self($payload); + + default: + return new self($payload); + } + } +} diff --git a/vendor/predis/predis/src/Session/Handler.php b/vendor/predis/predis/src/Session/Handler.php new file mode 100644 index 0000000..cecb9d5 --- /dev/null +++ b/vendor/predis/predis/src/Session/Handler.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 Predis\Session; + +use Predis\ClientInterface; + +/** + * Session handler class that relies on Predis\Client to store PHP's sessions + * data into one or multiple Redis servers. + * + * This class is mostly intended for PHP 5.4 but it can be used under PHP 5.3 + * provided that a polyfill for `SessionHandlerInterface` is defined by either + * you or an external package such as `symfony/http-foundation`. + * + * @author Daniele Alessandri + */ +class Handler implements \SessionHandlerInterface +{ + protected $client; + protected $ttl; + + /** + * @param ClientInterface $client Fully initialized client instance. + * @param array $options Session handler options. + */ + public function __construct(ClientInterface $client, array $options = array()) + { + $this->client = $client; + + if (isset($options['gc_maxlifetime'])) { + $this->ttl = (int) $options['gc_maxlifetime']; + } else { + $this->ttl = ini_get('session.gc_maxlifetime'); + } + } + + /** + * Registers this instance as the current session handler. + */ + public function register() + { + if (PHP_VERSION_ID >= 50400) { + session_set_save_handler($this, true); + } else { + session_set_save_handler( + array($this, 'open'), + array($this, 'close'), + array($this, 'read'), + array($this, 'write'), + array($this, 'destroy'), + array($this, 'gc') + ); + } + } + + /** + * {@inheritdoc} + */ + public function open($save_path, $session_id) + { + // NOOP + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + // NOOP + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + // NOOP + return true; + } + + /** + * {@inheritdoc} + */ + public function read($session_id) + { + if ($data = $this->client->get($session_id)) { + return $data; + } + + return ''; + } + /** + * {@inheritdoc} + */ + public function write($session_id, $session_data) + { + $this->client->setex($session_id, $this->ttl, $session_data); + + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($session_id) + { + $this->client->del($session_id); + + return true; + } + + /** + * Returns the underlying client instance. + * + * @return ClientInterface + */ + public function getClient() + { + return $this->client; + } + + /** + * Returns the session max lifetime value. + * + * @return int + */ + public function getMaxLifeTime() + { + return $this->ttl; + } +} diff --git a/vendor/predis/predis/src/Transaction/AbortedMultiExecException.php b/vendor/predis/predis/src/Transaction/AbortedMultiExecException.php new file mode 100644 index 0000000..b36f38a --- /dev/null +++ b/vendor/predis/predis/src/Transaction/AbortedMultiExecException.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 Predis\Transaction; + +use Predis\PredisException; + +/** + * Exception class that identifies a MULTI / EXEC transaction aborted by Redis. + * + * @author Daniele Alessandri + */ +class AbortedMultiExecException extends PredisException +{ + private $transaction; + + /** + * @param MultiExec $transaction Transaction that generated the exception. + * @param string $message Error message. + * @param int $code Error code. + */ + public function __construct(MultiExec $transaction, $message, $code = null) + { + parent::__construct($message, $code); + $this->transaction = $transaction; + } + + /** + * Returns the transaction that generated the exception. + * + * @return MultiExec + */ + public function getTransaction() + { + return $this->transaction; + } +} diff --git a/vendor/predis/predis/src/Transaction/MultiExec.php b/vendor/predis/predis/src/Transaction/MultiExec.php new file mode 100644 index 0000000..0cf1962 --- /dev/null +++ b/vendor/predis/predis/src/Transaction/MultiExec.php @@ -0,0 +1,461 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Transaction; + +use Predis\ClientContextInterface; +use Predis\ClientException; +use Predis\ClientInterface; +use Predis\Command\CommandInterface; +use Predis\CommunicationException; +use Predis\Connection\AggregateConnectionInterface; +use Predis\NotSupportedException; +use Predis\Protocol\ProtocolException; +use Predis\Response\ErrorInterface as ErrorResponseInterface; +use Predis\Response\ServerException; +use Predis\Response\Status as StatusResponse; + +/** + * Client-side abstraction of a Redis transaction based on MULTI / EXEC. + * + * {@inheritdoc} + * + * @author Daniele Alessandri + */ +class MultiExec implements ClientContextInterface +{ + private $state; + + protected $client; + protected $commands; + protected $exceptions = true; + protected $attempts = 0; + protected $watchKeys = array(); + protected $modeCAS = false; + + /** + * @param ClientInterface $client Client instance used by the transaction. + * @param array $options Initialization options. + */ + public function __construct(ClientInterface $client, array $options = null) + { + $this->assertClient($client); + + $this->client = $client; + $this->state = new MultiExecState(); + + $this->configure($client, $options ?: array()); + $this->reset(); + } + + /** + * Checks if the passed client instance satisfies the required conditions + * needed to initialize the transaction object. + * + * @param ClientInterface $client Client instance used by the transaction object. + * + * @throws NotSupportedException + */ + private function assertClient(ClientInterface $client) + { + if ($client->getConnection() instanceof AggregateConnectionInterface) { + throw new NotSupportedException( + 'Cannot initialize a MULTI/EXEC transaction over aggregate connections.' + ); + } + + if (!$client->getProfile()->supportsCommands(array('MULTI', 'EXEC', 'DISCARD'))) { + throw new NotSupportedException( + 'The current profile does not support MULTI, EXEC and DISCARD.' + ); + } + } + + /** + * Configures the transaction using the provided options. + * + * @param ClientInterface $client Underlying client instance. + * @param array $options Array of options for the transaction. + **/ + protected function configure(ClientInterface $client, array $options) + { + if (isset($options['exceptions'])) { + $this->exceptions = (bool) $options['exceptions']; + } else { + $this->exceptions = $client->getOptions()->exceptions; + } + + if (isset($options['cas'])) { + $this->modeCAS = (bool) $options['cas']; + } + + if (isset($options['watch']) && $keys = $options['watch']) { + $this->watchKeys = $keys; + } + + if (isset($options['retry'])) { + $this->attempts = (int) $options['retry']; + } + } + + /** + * Resets the state of the transaction. + */ + protected function reset() + { + $this->state->reset(); + $this->commands = new \SplQueue(); + } + + /** + * Initializes the transaction context. + */ + protected function initialize() + { + if ($this->state->isInitialized()) { + return; + } + + if ($this->modeCAS) { + $this->state->flag(MultiExecState::CAS); + } + + if ($this->watchKeys) { + $this->watch($this->watchKeys); + } + + $cas = $this->state->isCAS(); + $discarded = $this->state->isDiscarded(); + + if (!$cas || ($cas && $discarded)) { + $this->call('MULTI'); + + if ($discarded) { + $this->state->unflag(MultiExecState::CAS); + } + } + + $this->state->unflag(MultiExecState::DISCARDED); + $this->state->flag(MultiExecState::INITIALIZED); + } + + /** + * Dynamically invokes a Redis command with the specified arguments. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + * + * @return mixed + */ + public function __call($method, $arguments) + { + return $this->executeCommand( + $this->client->createCommand($method, $arguments) + ); + } + + /** + * Executes a Redis command bypassing the transaction logic. + * + * @param string $commandID Command ID. + * @param array $arguments Arguments for the command. + * + * @throws ServerException + * + * @return mixed + */ + protected function call($commandID, array $arguments = array()) + { + $response = $this->client->executeCommand( + $this->client->createCommand($commandID, $arguments) + ); + + if ($response instanceof ErrorResponseInterface) { + throw new ServerException($response->getMessage()); + } + + return $response; + } + + /** + * Executes the specified Redis command. + * + * @param CommandInterface $command Command instance. + * + * @throws AbortedMultiExecException + * @throws CommunicationException + * + * @return $this|mixed + */ + public function executeCommand(CommandInterface $command) + { + $this->initialize(); + + if ($this->state->isCAS()) { + return $this->client->executeCommand($command); + } + + $response = $this->client->getConnection()->executeCommand($command); + + if ($response instanceof StatusResponse && $response == 'QUEUED') { + $this->commands->enqueue($command); + } elseif ($response instanceof ErrorResponseInterface) { + throw new AbortedMultiExecException($this, $response->getMessage()); + } else { + $this->onProtocolError('The server did not return a +QUEUED status response.'); + } + + return $this; + } + + /** + * Executes WATCH against one or more keys. + * + * @param string|array $keys One or more keys. + * + * @throws NotSupportedException + * @throws ClientException + * + * @return mixed + */ + public function watch($keys) + { + if (!$this->client->getProfile()->supportsCommand('WATCH')) { + throw new NotSupportedException('WATCH is not supported by the current profile.'); + } + + if ($this->state->isWatchAllowed()) { + throw new ClientException('Sending WATCH after MULTI is not allowed.'); + } + + $response = $this->call('WATCH', is_array($keys) ? $keys : array($keys)); + $this->state->flag(MultiExecState::WATCH); + + return $response; + } + + /** + * Finalizes the transaction by executing MULTI on the server. + * + * @return MultiExec + */ + public function multi() + { + if ($this->state->check(MultiExecState::INITIALIZED | MultiExecState::CAS)) { + $this->state->unflag(MultiExecState::CAS); + $this->call('MULTI'); + } else { + $this->initialize(); + } + + return $this; + } + + /** + * Executes UNWATCH. + * + * @throws NotSupportedException + * + * @return MultiExec + */ + public function unwatch() + { + if (!$this->client->getProfile()->supportsCommand('UNWATCH')) { + throw new NotSupportedException( + 'UNWATCH is not supported by the current profile.' + ); + } + + $this->state->unflag(MultiExecState::WATCH); + $this->__call('UNWATCH', array()); + + return $this; + } + + /** + * Resets the transaction by UNWATCH-ing the keys that are being WATCHed and + * DISCARD-ing pending commands that have been already sent to the server. + * + * @return MultiExec + */ + public function discard() + { + if ($this->state->isInitialized()) { + $this->call($this->state->isCAS() ? 'UNWATCH' : 'DISCARD'); + + $this->reset(); + $this->state->flag(MultiExecState::DISCARDED); + } + + return $this; + } + + /** + * Executes the whole transaction. + * + * @return mixed + */ + public function exec() + { + return $this->execute(); + } + + /** + * Checks the state of the transaction before execution. + * + * @param mixed $callable Callback for execution. + * + * @throws \InvalidArgumentException + * @throws ClientException + */ + private function checkBeforeExecution($callable) + { + if ($this->state->isExecuting()) { + throw new ClientException( + 'Cannot invoke "execute" or "exec" inside an active transaction context.' + ); + } + + if ($callable) { + if (!is_callable($callable)) { + throw new \InvalidArgumentException('The argument must be a callable object.'); + } + + if (!$this->commands->isEmpty()) { + $this->discard(); + + throw new ClientException( + 'Cannot execute a transaction block after using fluent interface.' + ); + } + } elseif ($this->attempts) { + $this->discard(); + + throw new ClientException( + 'Automatic retries are supported only when a callable block is provided.' + ); + } + } + + /** + * Handles the actual execution of the whole transaction. + * + * @param mixed $callable Optional callback for execution. + * + * @throws CommunicationException + * @throws AbortedMultiExecException + * @throws ServerException + * + * @return array + */ + public function execute($callable = null) + { + $this->checkBeforeExecution($callable); + + $execResponse = null; + $attempts = $this->attempts; + + do { + if ($callable) { + $this->executeTransactionBlock($callable); + } + + if ($this->commands->isEmpty()) { + if ($this->state->isWatching()) { + $this->discard(); + } + + return; + } + + $execResponse = $this->call('EXEC'); + + if ($execResponse === null) { + if ($attempts === 0) { + throw new AbortedMultiExecException( + $this, 'The current transaction has been aborted by the server.' + ); + } + + $this->reset(); + + continue; + } + + break; + } while ($attempts-- > 0); + + $response = array(); + $commands = $this->commands; + $size = count($execResponse); + + if ($size !== count($commands)) { + $this->onProtocolError('EXEC returned an unexpected number of response items.'); + } + + for ($i = 0; $i < $size; ++$i) { + $cmdResponse = $execResponse[$i]; + + if ($cmdResponse instanceof ErrorResponseInterface && $this->exceptions) { + throw new ServerException($cmdResponse->getMessage()); + } + + $response[$i] = $commands->dequeue()->parseResponse($cmdResponse); + } + + return $response; + } + + /** + * Passes the current transaction object to a callable block for execution. + * + * @param mixed $callable Callback. + * + * @throws CommunicationException + * @throws ServerException + */ + protected function executeTransactionBlock($callable) + { + $exception = null; + $this->state->flag(MultiExecState::INSIDEBLOCK); + + try { + call_user_func($callable, $this); + } catch (CommunicationException $exception) { + // NOOP + } catch (ServerException $exception) { + // NOOP + } catch (\Exception $exception) { + $this->discard(); + } + + $this->state->unflag(MultiExecState::INSIDEBLOCK); + + if ($exception) { + throw $exception; + } + } + + /** + * Helper method for protocol errors encountered inside the transaction. + * + * @param string $message Error message. + */ + private function onProtocolError($message) + { + // Since a MULTI/EXEC block cannot be initialized when using aggregate + // connections we can safely assume that Predis\Client::getConnection() + // will return a Predis\Connection\NodeConnectionInterface instance. + CommunicationException::handle(new ProtocolException( + $this->client->getConnection(), $message + )); + } +} diff --git a/vendor/predis/predis/src/Transaction/MultiExecState.php b/vendor/predis/predis/src/Transaction/MultiExecState.php new file mode 100644 index 0000000..4bed42a --- /dev/null +++ b/vendor/predis/predis/src/Transaction/MultiExecState.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 Predis\Transaction; + +/** + * Utility class used to track the state of a MULTI / EXEC transaction. + * + * @author Daniele Alessandri + */ +class MultiExecState +{ + const INITIALIZED = 1; // 0b00001 + const INSIDEBLOCK = 2; // 0b00010 + const DISCARDED = 4; // 0b00100 + const CAS = 8; // 0b01000 + const WATCH = 16; // 0b10000 + + private $flags; + + /** + * + */ + public function __construct() + { + $this->flags = 0; + } + + /** + * Sets the internal state flags. + * + * @param int $flags Set of flags + */ + public function set($flags) + { + $this->flags = $flags; + } + + /** + * Gets the internal state flags. + * + * @return int + */ + public function get() + { + return $this->flags; + } + + /** + * Sets one or more flags. + * + * @param int $flags Set of flags + */ + public function flag($flags) + { + $this->flags |= $flags; + } + + /** + * Resets one or more flags. + * + * @param int $flags Set of flags + */ + public function unflag($flags) + { + $this->flags &= ~$flags; + } + + /** + * Returns if the specified flag or set of flags is set. + * + * @param int $flags Flag + * + * @return bool + */ + public function check($flags) + { + return ($this->flags & $flags) === $flags; + } + + /** + * Resets the state of a transaction. + */ + public function reset() + { + $this->flags = 0; + } + + /** + * Returns the state of the RESET flag. + * + * @return bool + */ + public function isReset() + { + return $this->flags === 0; + } + + /** + * Returns the state of the INITIALIZED flag. + * + * @return bool + */ + public function isInitialized() + { + return $this->check(self::INITIALIZED); + } + + /** + * Returns the state of the INSIDEBLOCK flag. + * + * @return bool + */ + public function isExecuting() + { + return $this->check(self::INSIDEBLOCK); + } + + /** + * Returns the state of the CAS flag. + * + * @return bool + */ + public function isCAS() + { + return $this->check(self::CAS); + } + + /** + * Returns if WATCH is allowed in the current state. + * + * @return bool + */ + public function isWatchAllowed() + { + return $this->check(self::INITIALIZED) && !$this->check(self::CAS); + } + + /** + * Returns the state of the WATCH flag. + * + * @return bool + */ + public function isWatching() + { + return $this->check(self::WATCH); + } + + /** + * Returns the state of the DISCARDED flag. + * + * @return bool + */ + public function isDiscarded() + { + return $this->check(self::DISCARDED); + } +} diff --git a/vendor/psr/cache/CHANGELOG.md b/vendor/psr/cache/CHANGELOG.md new file mode 100644 index 0000000..58ddab0 --- /dev/null +++ b/vendor/psr/cache/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.0.1 - 2016-08-06 + +### Fixed + +- Make spacing consistent in phpdoc annotations php-fig/cache#9 - chalasr +- Fix grammar in phpdoc annotations php-fig/cache#10 - chalasr +- Be more specific in docblocks that `getItems()` and `deleteItems()` take an array of strings (`string[]`) compared to just `array` php-fig/cache#8 - GrahamCampbell +- For `expiresAt()` and `expiresAfter()` in CacheItemInterface fix docblock to specify null as a valid parameters as well as an implementation of DateTimeInterface php-fig/cache#7 - GrahamCampbell + +## 1.0.0 - 2015-12-11 + +Initial stable release; reflects accepted PSR-6 specification diff --git a/vendor/psr/cache/LICENSE.txt b/vendor/psr/cache/LICENSE.txt new file mode 100644 index 0000000..b1c2c97 --- /dev/null +++ b/vendor/psr/cache/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2015 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/psr/cache/README.md b/vendor/psr/cache/README.md new file mode 100644 index 0000000..c8706ce --- /dev/null +++ b/vendor/psr/cache/README.md @@ -0,0 +1,9 @@ +PSR Cache +========= + +This repository holds all interfaces defined by +[PSR-6](http://www.php-fig.org/psr/psr-6/). + +Note that this is not a Cache implementation of its own. It is merely an +interface that describes a Cache implementation. See the specification for more +details. diff --git a/vendor/psr/cache/composer.json b/vendor/psr/cache/composer.json new file mode 100644 index 0000000..e828fec --- /dev/null +++ b/vendor/psr/cache/composer.json @@ -0,0 +1,25 @@ +{ + "name": "psr/cache", + "description": "Common interface for caching libraries", + "keywords": ["psr", "psr-6", "cache"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/cache/src/CacheException.php b/vendor/psr/cache/src/CacheException.php new file mode 100644 index 0000000..e27f22f --- /dev/null +++ b/vendor/psr/cache/src/CacheException.php @@ -0,0 +1,10 @@ +=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/container/src/ContainerExceptionInterface.php b/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 0000000..d35c6b4 --- /dev/null +++ b/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,13 @@ +=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/http-message/src/MessageInterface.php b/vendor/psr/http-message/src/MessageInterface.php new file mode 100644 index 0000000..dd46e5e --- /dev/null +++ b/vendor/psr/http-message/src/MessageInterface.php @@ -0,0 +1,187 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name); + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name); + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value); + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value); + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader($name); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(); + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body); +} diff --git a/vendor/psr/http-message/src/RequestInterface.php b/vendor/psr/http-message/src/RequestInterface.php new file mode 100644 index 0000000..a96d4fd --- /dev/null +++ b/vendor/psr/http-message/src/RequestInterface.php @@ -0,0 +1,129 @@ +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(); + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return static + */ + public function withQueryParams(array $query); + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(); + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return static + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles); + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return static + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data); + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes(); + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return static + */ + public function withAttribute($name, $value); + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return static + */ + public function withoutAttribute($name); +} diff --git a/vendor/psr/http-message/src/StreamInterface.php b/vendor/psr/http-message/src/StreamInterface.php new file mode 100644 index 0000000..f68f391 --- /dev/null +++ b/vendor/psr/http-message/src/StreamInterface.php @@ -0,0 +1,158 @@ + + * [user-info@]host[:port] + * + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(); + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(); + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(); + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(); + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(); + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(); + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(); + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return static A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme); + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return static A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null); + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return static A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host); + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return static A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port); + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return static A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path); + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return static A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query); + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return static A new instance with the specified fragment. + */ + public function withFragment($fragment); + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(); +} diff --git a/vendor/psr/log/.gitignore b/vendor/psr/log/.gitignore new file mode 100644 index 0000000..22d0d82 --- /dev/null +++ b/vendor/psr/log/.gitignore @@ -0,0 +1 @@ +vendor diff --git a/vendor/psr/log/LICENSE b/vendor/psr/log/LICENSE new file mode 100644 index 0000000..474c952 --- /dev/null +++ b/vendor/psr/log/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/psr/log/Psr/Log/AbstractLogger.php b/vendor/psr/log/Psr/Log/AbstractLogger.php new file mode 100644 index 0000000..90e721a --- /dev/null +++ b/vendor/psr/log/Psr/Log/AbstractLogger.php @@ -0,0 +1,128 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000..67f852d --- /dev/null +++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000..e695046 --- /dev/null +++ b/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000..c8f7293 --- /dev/null +++ b/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php new file mode 100644 index 0000000..9ecb6c4 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php @@ -0,0 +1,146 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $closed = fopen('php://memory', 'r'); + fclose($closed); + + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + 'closed' => $closed, + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} + +class DummyTest +{ + public function __toString() + { + return 'DummyTest'; + } +} diff --git a/vendor/psr/log/Psr/Log/Test/TestLogger.php b/vendor/psr/log/Psr/Log/Test/TestLogger.php new file mode 100644 index 0000000..1be3230 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/TestLogger.php @@ -0,0 +1,147 @@ + $level, + 'message' => $message, + 'context' => $context, + ]; + + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_string($record)) { + $record = ['message' => $record]; + } + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + return true; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses(callable $predicate, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + return false; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = strtolower($matches[2]); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + return call_user_func_array([$this, $genericMethod], $args); + } + } + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } + + public function reset() + { + $this->records = []; + $this->recordsByLevel = []; + } +} diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md new file mode 100644 index 0000000..a9f20c4 --- /dev/null +++ b/vendor/psr/log/README.md @@ -0,0 +1,58 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Installation +------------ + +```bash +composer require psr/log +``` + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + try { + $this->doSomethingElse(); + } catch (Exception $exception) { + $this->logger->error('Oh no!', array('exception' => $exception)); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json new file mode 100644 index 0000000..3f6d4ee --- /dev/null +++ b/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/vendor/psr/simple-cache/.editorconfig b/vendor/psr/simple-cache/.editorconfig new file mode 100644 index 0000000..48542cb --- /dev/null +++ b/vendor/psr/simple-cache/.editorconfig @@ -0,0 +1,12 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/vendor/psr/simple-cache/LICENSE.md b/vendor/psr/simple-cache/LICENSE.md new file mode 100644 index 0000000..e49a7c8 --- /dev/null +++ b/vendor/psr/simple-cache/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) 2016 PHP Framework Interoperability Group + +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. diff --git a/vendor/psr/simple-cache/README.md b/vendor/psr/simple-cache/README.md new file mode 100644 index 0000000..43641d1 --- /dev/null +++ b/vendor/psr/simple-cache/README.md @@ -0,0 +1,8 @@ +PHP FIG Simple Cache PSR +======================== + +This repository holds all interfaces related to PSR-16. + +Note that this is not a cache implementation of its own. It is merely an interface that describes a cache implementation. See [the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) for more details. + +You can find implementations of the specification by looking for packages providing the [psr/simple-cache-implementation](https://packagist.org/providers/psr/simple-cache-implementation) virtual package. diff --git a/vendor/psr/simple-cache/composer.json b/vendor/psr/simple-cache/composer.json new file mode 100644 index 0000000..2978fa5 --- /dev/null +++ b/vendor/psr/simple-cache/composer.json @@ -0,0 +1,25 @@ +{ + "name": "psr/simple-cache", + "description": "Common interfaces for simple caching", + "keywords": ["psr", "psr-16", "cache", "simple-cache", "caching"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/simple-cache/src/CacheException.php b/vendor/psr/simple-cache/src/CacheException.php new file mode 100644 index 0000000..eba5381 --- /dev/null +++ b/vendor/psr/simple-cache/src/CacheException.php @@ -0,0 +1,10 @@ + value pairs. Cache keys that do not exist or are stale will have $default as value. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function getMultiple($keys, $default = null); + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * @param iterable $values A list of key => value pairs for a multiple-set operation. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $values is neither an array nor a Traversable, + * or if any of the $values are not a legal value. + */ + public function setMultiple($values, $ttl = null); + + /** + * Deletes multiple cache items in a single operation. + * + * @param iterable $keys A list of string-based keys to be deleted. + * + * @return bool True if the items were successfully removed. False if there was an error. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function deleteMultiple($keys); + + /** + * Determines whether an item is present in the cache. + * + * NOTE: It is recommended that has() is only to be used for cache warming type purposes + * and not to be used within your live applications operations for get/set, as this method + * is subject to a race condition where your has() will return true and immediately after, + * another script can remove it making the state of your app out of date. + * + * @param string $key The cache item key. + * + * @return bool + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function has($key); +} diff --git a/vendor/psr/simple-cache/src/InvalidArgumentException.php b/vendor/psr/simple-cache/src/InvalidArgumentException.php new file mode 100644 index 0000000..6a9524a --- /dev/null +++ b/vendor/psr/simple-cache/src/InvalidArgumentException.php @@ -0,0 +1,13 @@ += 5.3. + +[![Build Status](https://travis-ci.org/ralouphie/getallheaders.svg?branch=master)](https://travis-ci.org/ralouphie/getallheaders) +[![Coverage Status](https://coveralls.io/repos/ralouphie/getallheaders/badge.png?branch=master)](https://coveralls.io/r/ralouphie/getallheaders?branch=master) +[![Latest Stable Version](https://poser.pugx.org/ralouphie/getallheaders/v/stable.png)](https://packagist.org/packages/ralouphie/getallheaders) +[![Latest Unstable Version](https://poser.pugx.org/ralouphie/getallheaders/v/unstable.png)](https://packagist.org/packages/ralouphie/getallheaders) +[![License](https://poser.pugx.org/ralouphie/getallheaders/license.png)](https://packagist.org/packages/ralouphie/getallheaders) + + +This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php). + +## Install + +For PHP version **`>= 5.6`**: + +``` +composer require ralouphie/getallheaders +``` + +For PHP version **`< 5.6`**: + +``` +composer require ralouphie/getallheaders "^2" +``` diff --git a/vendor/ralouphie/getallheaders/composer.json b/vendor/ralouphie/getallheaders/composer.json new file mode 100644 index 0000000..de8ce62 --- /dev/null +++ b/vendor/ralouphie/getallheaders/composer.json @@ -0,0 +1,26 @@ +{ + "name": "ralouphie/getallheaders", + "description": "A polyfill for getallheaders.", + "license": "MIT", + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^5 || ^6.5", + "php-coveralls/php-coveralls": "^2.1" + }, + "autoload": { + "files": ["src/getallheaders.php"] + }, + "autoload-dev": { + "psr-4": { + "getallheaders\\Tests\\": "tests/" + } + } +} diff --git a/vendor/ralouphie/getallheaders/src/getallheaders.php b/vendor/ralouphie/getallheaders/src/getallheaders.php new file mode 100644 index 0000000..c7285a5 --- /dev/null +++ b/vendor/ralouphie/getallheaders/src/getallheaders.php @@ -0,0 +1,46 @@ + 'Content-Type', + 'CONTENT_LENGTH' => 'Content-Length', + 'CONTENT_MD5' => 'Content-Md5', + ); + + foreach ($_SERVER as $key => $value) { + if (substr($key, 0, 5) === 'HTTP_') { + $key = substr($key, 5); + if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) { + $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); + $headers[$key] = $value; + } + } elseif (isset($copy_server[$key])) { + $headers[$copy_server[$key]] = $value; + } + } + + if (!isset($headers['Authorization'])) { + if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { + $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (isset($_SERVER['PHP_AUTH_USER'])) { + $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; + $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); + } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { + $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; + } + } + + return $headers; + } + +} diff --git a/vendor/ramsey/uuid/CHANGELOG.md b/vendor/ramsey/uuid/CHANGELOG.md new file mode 100644 index 0000000..f2f1548 --- /dev/null +++ b/vendor/ramsey/uuid/CHANGELOG.md @@ -0,0 +1,643 @@ +# ramsey/uuid Changelog + +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] + +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Security + + +## [3.9.2] - 2019-12-17 + +### Fixed + +* Check whether files returned by `/sys/class/net/*/address` are readable + before attempting to read them. This avoids a PHP warning that was being + emitted on hosts that do not grant permission to read these files. + + +## [3.9.1] - 2019-12-01 + +### Fixed + +* Fix `RandomNodeProvider` behavior on 32-bit systems. The `RandomNodeProvider` + was converting a 6-byte string to a decimal number, which is a 48-bit, + unsigned integer. This caused problems on 32-bit systems and has now been + resolved. + + +## [3.9.0] - 2019-11-30 + +### Added + +* Add function API as convenience. The functions are available in the + `Ramsey\Uuid` namespace. + * `v1(int|string|null $node = null, int|null $clockSeq = null): string` + * `v3(string|UuidInterface $ns, string $name): string` + * `v4(): string` + * `v5(string|UuidInterface $ns, string $name): string` + +### Changed + +* Use paragonie/random-lib instead of ircmaxell/random-lib. This is a + non-breaking change. +* Use a high-strength generator by default, when using `RandomLibAdapter`. This + is a non-breaking change. + +### Deprecated + +These will be removed in ramsey/uuid version 4.0.0: + +* `MtRandGenerator`, `OpenSslGenerator`, and `SodiumRandomGenerator` are + deprecated in favor of using the default `RandomBytesGenerator`. + +### Fixed + +* Set `ext-json` as a required dependency in `composer.json`. +* Use `PHP_OS` instead of `php_uname()` when determining the system OS, for + cases when `php_uname()` is disabled for security reasons. + + +## [3.8.0] - 2018-07-19 + +### Added + +* Support discovery of MAC addresses on FreeBSD systems +* Use a polyfill to provide PHP ctype functions when running on systems where the + ctype functions are not part of the PHP build +* Disallow a trailing newline character when validating UUIDs +* Annotate thrown exceptions for improved IDE hinting + + +## [3.7.3] - 2018-01-19 + +### Fixed + +* Gracefully handle cases where `glob()` returns false when searching + `/sys/class/net/*/address` files on Linux +* Fix off-by-one error in `DefaultTimeGenerator` + +### Security + +* Switch to `random_int()` from `mt_rand()` for better random numbers + + +## [3.7.2] - 2018-01-13 + +### Fixed + +* Check sysfs on Linux to determine the node identifier; this provides a + reliable way to identify the node on Docker images, etc. + + +## [3.7.1] - 2017-09-22 + +### Fixed + +* Set the multicast bit for random nodes, according to RFC 4122, §4.5 + +### Security + +* Use `random_bytes()` when generating random nodes + + +## [3.7.0] - 2017-08-04 + +### Added + +* Add the following UUID version constants: + * `Uuid::UUID_TYPE_TIME` + * `Uuid::UUID_TYPE_IDENTIFIER` + * `Uuid::UUID_TYPE_HASH_MD5` + * `Uuid::UUID_TYPE_RANDOM` + * `Uuid::UUID_TYPE_HASH_SHA1` + + +## [3.6.1] - 2017-03-26 + +### Fixed + +* Optimize UUID string decoding by using `str_pad()` instead of `sprintf()` + + +## [3.6.0] - 2017-03-18 + +### Added + +* Add `InvalidUuidStringException`, which is thrown when attempting to decode an + invalid string UUID; this does not introduce any BC issues, since the new + exception inherits from the previously used `InvalidArgumentException` + +### Fixed + +* Improve memory usage when generating large quantities of UUIDs (use `str_pad()` + and `dechex()` instead of `sprintf()`) + + +## [3.5.2] - 2016-11-22 + +### Fixed + +* Improve test coverage + + +## [3.5.1] - 2016-10-02 + +### Fixed + +* Fix issue where the same UUIDs were not being treated as equal when using + mixed cases + + +## [3.5.0] - 2016-08-02 + +### Added + +* Add `OrderedTimeCodec` to store UUID in an optimized way for InnoDB + +### Fixed + +* Fix invalid node generation in `RandomNodeProvider` +* Avoid multiple unnecessary system calls by caching failed attempt to retrieve + system node + + +## [3.4.1] - 2016-04-23 + +### Fixed + +* Fix test that violated a PHP CodeSniffer rule, breaking the build + + +## [3.4.0] - 2016-04-23 + +### Added + +* Add `TimestampFirstCombCodec` and `TimestampLastCombCodec` codecs to provide + the ability to generate [COMB sequential UUIDs] with the timestamp encoded as + either the first 48 bits or the last 48 bits +* Improve logic of `CombGenerator` for COMB sequential UUIDs + + +## [3.3.0] - 2016-03-22 + +### Security + +* Drop the use of OpenSSL as a fallback and use [paragonie/random_compat] to + support `RandomBytesGenerator` in versions of PHP earlier than 7.0; + this addresses and fixes the [collision issue] + + +## [3.2.0] - 2016-02-17 + +### Added + +* Add `SodiumRandomGenerator` to allow use of the [PECL libsodium extension] as + a random bytes generator when creating UUIDs + + +## [3.1.0] - 2015-12-17 + +### Added + +* Implement the PHP `Serializable` interface to provide the ability to + serialize/unserialize UUID objects + + +## [3.0.1] - 2015-10-21 + +### Added + +* Adopt the [Contributor Code of Conduct] for this project + + +## [3.0.0] - 2015-09-28 + +The 3.0.0 release represents a significant step for the ramsey/uuid library. +While the simple and familiar API used in previous versions remains intact, this +release provides greater flexibility to integrators, including the ability to +inject your own number generators, UUID codecs, node and time providers, and +more. + +*Please note: The changelog for 3.0.0 includes all notes from the alpha and beta +versions leading up to this release.* + +### Added + +* Add a number of generators that may be used to override the library defaults + for generating random bytes (version 4) or time-based (version 1) UUIDs + * `CombGenerator` to allow generation of sequential UUIDs + * `OpenSslGenerator` to generate random bytes on systems where + `openssql_random_pseudo_bytes()` is present + * `MtRandGenerator` to provide a fallback in the event other random generators + are not present + * `RandomLibAdapter` to allow use of [ircmaxell/random-lib] + * `RandomBytesGenerator` for use with PHP 7; ramsey/uuid will default to use + this generator when running on PHP 7 + * Refactor time-based (version 1) UUIDs into a `TimeGeneratorInterface` to + allow for other sources to generate version 1 UUIDs in this library + * `PeclUuidTimeGenerator` and `PeclUuidRandomGenerator` for creating version + 1 or version 4 UUIDs using the pecl-uuid extension +* Add a `setTimeGenerator` method on `UuidFactory` to override the default time + generator +* Add option to enable `PeclUuidTimeGenerator` via `FeatureSet` +* Support GUID generation by configuring a `FeatureSet` to use GUIDs +* Allow UUIDs to be serialized as JSON through `JsonSerializable` + +### Changed + +* Change root namespace from "Rhumsaa" to "Ramsey;" in most cases, simply + making this change in your applications is the only upgrade path you will + need—everything else should work as expected +* No longer consider `Uuid` class as `final`; everything is now based around + interfaces and factories, allowing you to use this package as a base to + implement other kinds of UUIDs with different dependencies +* Return an object of type `DegradedUuid` on 32-bit systems to indicate that + certain features are not available +* Default `RandomLibAdapter` to a medium-strength generator with + [ircmaxell/random-lib]; this is configurable, so other generator strengths may + be used + +### Removed + +* Remove `PeclUuidFactory` in favor of using pecl-uuid with generators +* Remove `timeConverter` and `timeProvider` properties, setters, and getters in + both `FeatureSet` and `UuidFactory` as those are now exclusively used by the + default `TimeGenerator` +* Move UUID [Doctrine field type] to [ramsey/uuid-doctrine] +* Move `uuid` console application to [ramsey/uuid-console] +* Remove `Uuid::VERSION` package version constant + +### Fixed + +* Improve GUID support to ensure that: + * On little endian (LE) architectures, the byte order of the first three + fields is LE + * On big endian (BE) architectures, it is the same as a GUID + * String representation is always the same +* Fix exception message for `DegradedNumberConverter::fromHex()` + + +## [3.0.0-beta1] - 2015-08-31 + +### Fixed + +* Improve GUID support to ensure that: + * On little endian (LE) architectures, the byte order of the first three + fields is LE + * On big endian (BE) architectures, it is the same as a GUID + * String representation is always the same +* Fix exception message for `DegradedNumberConverter::fromHex()` + + +## [3.0.0-alpha3] - 2015-07-28 + +### Added + +* Enable use of custom `TimeGenerator` implementations +* Add a `setTimeGenerator` method on `UuidFactory` to override the default time + generator +* Add option to enable `PeclUuidTimeGenerator` via `FeatureSet` + +### Removed + +* Remove `timeConverter` and `timeProvider` properties, setters, and getters in + both `FeatureSet` and `UuidFactory` as those are now exclusively used by the + default `TimeGenerator` + + +## [3.0.0-alpha2] - 2015-07-28 + +### Added + +* Refactor time-based (version 1) UUIDs into a `TimeGeneratorInterface` to allow + for other sources to generate version 1 UUIDs in this library +* Add `PeclUuidTimeGenerator` and `PeclUuidRandomGenerator` for creating version + 1 or version 4 UUIDs using the pecl-uuid extension +* Add `RandomBytesGenerator` for use with PHP 7. ramsey/uuid will default to use + this generator when running on PHP 7 + +### Changed + +* Default `RandomLibAdapter` to a medium-strength generator with + [ircmaxell/random-lib]; this is configurable, so other generator strengths may + be used + +### Removed + +* Remove `PeclUuidFactory` in favor of using pecl-uuid with generators + + +## [3.0.0-alpha1] - 2015-07-16 + +### Added + +* Allow dependency injection through `UuidFactory` and/or extending `FeatureSet` + to override any package defaults +* Add a number of generators that may be used to override the library defaults: + * `CombGenerator` to allow generation of sequential UUIDs + * `OpenSslGenerator` to generate random bytes on systems where + `openssql_random_pseudo_bytes()` is present + * `MtRandGenerator` to provide a fallback in the event other random generators + are not present + * `RandomLibAdapter` to allow use of [ircmaxell/random-lib] +* Support GUID generation by configuring a `FeatureSet` to use GUIDs +* Allow UUIDs to be serialized as JSON through `JsonSerializable` + +### Changed + +* Change root namespace from "Rhumsaa" to "Ramsey;" in most cases, simply + making this change in your applications is the only upgrade path you will + need—everything else should work as expected +* No longer consider `Uuid` class as `final`; everything is now based around + interfaces and factories, allowing you to use this package as a base to + implement other kinds of UUIDs with different dependencies +* Return an object of type `DegradedUuid` on 32-bit systems to indicate that + certain features are not available + +### Removed + +* Move UUID [Doctrine field type] to [ramsey/uuid-doctrine] +* Move `uuid` console application to [ramsey/uuid-console] +* Remove `Uuid::VERSION` package version constant + + +## [2.9.0] - 2016-03-22 + +### Security + +* Drop the use of OpenSSL as a fallback and use [paragonie/random_compat] to + support `RandomBytesGenerator` in versions of PHP earlier than 7.0; + this addresses and fixes the [collision issue] + + +## [2.8.4] - 2015-12-17 + +### Added + +* Add support for symfony/console v3 in the `uuid` CLI application + + +## [2.8.3] - 2015-08-31 + +### Fixed + +* Fix exception message in `Uuid::calculateUuidTime()` + + +## [2.8.2] - 2015-07-23 + +### Fixed + +* Ensure the release tag makes it into the rhumsaa/uuid package + + +## [2.8.1] - 2015-06-16 + +### Fixed + +* Use `passthru()` and output buffering in `getIfconfig()` +* Cache the system node in a static variable so that we process it only once per + runtime + + +## [2.8.0] - 2014-11-09 + +### Added + +* Add static `fromInteger()` method to create UUIDs from string integer or + `Moontoast\Math\BigNumber` + +### Fixed + +* Improve Doctrine conversion to Uuid or string for the ramsey/uuid [Doctrine + field type] + + +## [2.7.4] - 2014-10-29 + +### Fixed + +* Change loop in `generateBytes()` from `foreach` to `for` + + +## [2.7.3] - 2014-08-27 + +### Fixed + +* Fix upper range for `mt_rand` used in version 4 UUIDs + + +## [2.7.2] - 2014-07-28 + +### Changed + +* Upgrade to PSR-4 autoloading + + +## [2.7.1] - 2014-02-19 + +### Fixed + +* Move moontoast/math and symfony/console to require-dev +* Support symfony/console 2.3 (LTS version) + + +## [2.7.0] - 2014-01-31 + +### Added + +* Add `Uuid::VALID_PATTERN` constant containing a UUID validation regex pattern + + +## [2.6.1] - 2014-01-27 + +### Fixed + +* Fix bug where `uuid` console application could not find the Composer + autoloader when installed in another project + + +## [2.6.0] - 2014-01-17 + +### Added + +* Introduce `uuid` console application for generating and decoding UUIDs from + CLI (run `./bin/uuid` for details) +* Add `Uuid::getInteger()` to retrieve a `Moontoast\Math\BigNumber` + representation of the 128-bit integer representing the UUID +* Add `Uuid::getHex()` to retrieve the hexadecimal representation of the UUID +* Use `netstat` on Linux to capture the node for a version 1 UUID +* Require moontoast/math as part of the regular package requirements + + +## [2.5.0] - 2013-10-30 + +### Added + +* Use `openssl_random_pseudo_bytes()`, if available, to generate random bytes + + +## [2.4.0] - 2013-07-29 + +### Added + +* Return `null` from `Uuid::getVersion()` if the UUID isn't an RFC 4122 variant +* Support string UUIDs without dashes passed to `Uuid::fromString()` + + +## [2.3.0] - 2013-07-16 + +### Added + +* Support creation of UUIDs from bytes with `Uuid::fromBytes()` + + +## [2.2.0] - 2013-07-04 + +### Added + +* Add `Doctrine\UuidType::requiresSQLCommentHint()` method + + +## [2.1.2] - 2013-07-03 + +### Fixed + +* Fix cases where the system node was coming back with uppercase hexadecimal + digits; this ensures that case in the node is converted to lowercase + + +## [2.1.1] - 2013-04-29 + +### Fixed + +* Fix bug in `Uuid::isValid()` where the NIL UUID was not reported as valid + + +## [2.1.0] - 2013-04-15 + +### Added + +* Allow checking the validity of a UUID through the `Uuid::isValid()` method + + +## [2.0.0] - 2013-02-11 + +### Added + +* Support UUID generation on 32-bit platforms + +### Changed + +* Mark `Uuid` class `final` +* Require moontoast/math on 64-bit platforms for + `Uuid::getLeastSignificantBits()` and `Uuid::getMostSignificantBits()`; the + integers returned by these methods are *unsigned* 64-bit integers and + unsupported even on 64-bit builds of PHP +* Move `UnsupportedOperationException` to the `Exception` subnamespace + + +## [1.1.2] - 2012-11-29 + +### Fixed + +* Relax [Doctrine field type] conversion rules for UUIDs + + +## [1.1.1] - 2012-08-27 + +### Fixed + +* Remove `final` keyword from `Uuid` class + + +## [1.1.0] - 2012-08-06 + +### Added + +* Support ramsey/uuid UUIDs as a Doctrine Database Abstraction Layer (DBAL) + field mapping type + + +## [1.0.0] - 2012-07-19 + +### Added + +* Support generation of version 1, 3, 4, and 5 UUIDs + + +[comb sequential uuids]: http://www.informit.com/articles/article.aspx?p=25862&seqNum=7 +[paragonie/random_compat]: https://github.com/paragonie/random_compat +[collision issue]: https://github.com/ramsey/uuid/issues/80 +[contributor code of conduct]: https://github.com/ramsey/uuid/blob/master/.github/CODE_OF_CONDUCT.md +[pecl libsodium extension]: http://pecl.php.net/package/libsodium +[ircmaxell/random-lib]: https://github.com/ircmaxell/RandomLib +[doctrine field type]: http://doctrine-dbal.readthedocs.org/en/latest/reference/types.html +[ramsey/uuid-doctrine]: https://github.com/ramsey/uuid-doctrine +[ramsey/uuid-console]: https://github.com/ramsey/uuid-console + +[unreleased]: https://github.com/ramsey/uuid/compare/3.9.2...HEAD +[3.9.2]: https://github.com/ramsey/uuid/compare/3.9.1...3.9.2 +[3.9.1]: https://github.com/ramsey/uuid/compare/3.9.0...3.9.1 +[3.9.0]: https://github.com/ramsey/uuid/compare/3.8.0...3.9.0 +[3.8.0]: https://github.com/ramsey/uuid/compare/3.7.3...3.8.0 +[3.7.3]: https://github.com/ramsey/uuid/compare/3.7.2...3.7.3 +[3.7.2]: https://github.com/ramsey/uuid/compare/3.7.1...3.7.2 +[3.7.1]: https://github.com/ramsey/uuid/compare/3.7.0...3.7.1 +[3.7.0]: https://github.com/ramsey/uuid/compare/3.6.1...3.7.0 +[3.6.1]: https://github.com/ramsey/uuid/compare/3.6.0...3.6.1 +[3.6.0]: https://github.com/ramsey/uuid/compare/3.5.2...3.6.0 +[3.5.2]: https://github.com/ramsey/uuid/compare/3.5.1...3.5.2 +[3.5.1]: https://github.com/ramsey/uuid/compare/3.5.0...3.5.1 +[3.5.0]: https://github.com/ramsey/uuid/compare/3.4.1...3.5.0 +[3.4.1]: https://github.com/ramsey/uuid/compare/3.4.0...3.4.1 +[3.4.0]: https://github.com/ramsey/uuid/compare/3.3.0...3.4.0 +[3.3.0]: https://github.com/ramsey/uuid/compare/3.2.0...3.3.0 +[3.2.0]: https://github.com/ramsey/uuid/compare/3.1.0...3.2.0 +[3.1.0]: https://github.com/ramsey/uuid/compare/3.0.1...3.1.0 +[3.0.1]: https://github.com/ramsey/uuid/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/ramsey/uuid/compare/3.0.0-beta1...3.0.0 +[3.0.0-beta1]: https://github.com/ramsey/uuid/compare/3.0.0-alpha3...3.0.0-beta1 +[3.0.0-alpha3]: https://github.com/ramsey/uuid/compare/3.0.0-alpha2...3.0.0-alpha3 +[3.0.0-alpha2]: https://github.com/ramsey/uuid/compare/3.0.0-alpha1...3.0.0-alpha2 +[3.0.0-alpha1]: https://github.com/ramsey/uuid/compare/2.9.0...3.0.0-alpha1 +[2.9.0]: https://github.com/ramsey/uuid/compare/2.8.4...2.9.0 +[2.8.4]: https://github.com/ramsey/uuid/compare/2.8.3...2.8.4 +[2.8.3]: https://github.com/ramsey/uuid/compare/2.8.2...2.8.3 +[2.8.2]: https://github.com/ramsey/uuid/compare/2.8.1...2.8.2 +[2.8.1]: https://github.com/ramsey/uuid/compare/2.8.0...2.8.1 +[2.8.0]: https://github.com/ramsey/uuid/compare/2.7.4...2.8.0 +[2.7.4]: https://github.com/ramsey/uuid/compare/2.7.3...2.7.4 +[2.7.3]: https://github.com/ramsey/uuid/compare/2.7.2...2.7.3 +[2.7.2]: https://github.com/ramsey/uuid/compare/2.7.1...2.7.2 +[2.7.1]: https://github.com/ramsey/uuid/compare/2.7.0...2.7.1 +[2.7.0]: https://github.com/ramsey/uuid/compare/2.6.1...2.7.0 +[2.6.1]: https://github.com/ramsey/uuid/compare/2.6.0...2.6.1 +[2.6.0]: https://github.com/ramsey/uuid/compare/2.5.0...2.6.0 +[2.5.0]: https://github.com/ramsey/uuid/compare/2.4.0...2.5.0 +[2.4.0]: https://github.com/ramsey/uuid/compare/2.3.0...2.4.0 +[2.3.0]: https://github.com/ramsey/uuid/compare/2.2.0...2.3.0 +[2.2.0]: https://github.com/ramsey/uuid/compare/2.1.2...2.2.0 +[2.1.2]: https://github.com/ramsey/uuid/compare/2.1.1...2.1.2 +[2.1.1]: https://github.com/ramsey/uuid/compare/2.1.0...2.1.1 +[2.1.0]: https://github.com/ramsey/uuid/compare/2.0.0...2.1.0 +[2.0.0]: https://github.com/ramsey/uuid/compare/1.1.2...2.0.0 +[1.1.2]: https://github.com/ramsey/uuid/compare/1.1.1...1.1.2 +[1.1.1]: https://github.com/ramsey/uuid/compare/1.1.0...1.1.1 +[1.1.0]: https://github.com/ramsey/uuid/compare/1.0.0...1.1.0 +[1.0.0]: https://github.com/ramsey/uuid/commits/1.0.0 diff --git a/vendor/ramsey/uuid/LICENSE b/vendor/ramsey/uuid/LICENSE new file mode 100644 index 0000000..f6f7e80 --- /dev/null +++ b/vendor/ramsey/uuid/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2012-2019 Ben Ramsey + +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/ramsey/uuid/README.md b/vendor/ramsey/uuid/README.md new file mode 100644 index 0000000..95d4c21 --- /dev/null +++ b/vendor/ramsey/uuid/README.md @@ -0,0 +1,185 @@ +# ramsey/uuid + +*NOTICE: Formerly known as `rhumsaa/uuid`, The package and namespace names have +changed to `ramsey/uuid` and `Ramsey\Uuid`, respectively.* + +[![Source Code][badge-source]][source] +[![Latest Version][badge-release]][release] +[![Software License][badge-license]][license] +[![PHP Version][badge-php]][php] +[![Build Status][badge-build]][build] +[![Coverage Status][badge-coverage]][coverage] +[![Total Downloads][badge-downloads]][downloads] + +ramsey/uuid is a PHP 5.4+ library for generating and working with +[RFC 4122][rfc4122] version 1, 3, 4, and 5 universally unique identifiers +(UUID). + +This project adheres to a [Contributor Code of Conduct][conduct]. By +participating in this project and its community, you are expected to uphold this +code. + +From [Wikipedia](http://en.wikipedia.org/wiki/Universally_unique_identifier): + +> The intent of UUIDs is to enable distributed systems to uniquely identify +> information without significant central coordination. In this context the word +> unique should be taken to mean "practically unique" rather than "guaranteed +> unique". Since the identifiers have a finite size, it is possible for two +> differing items to share the same identifier. The identifier size and +> generation process need to be selected so as to make this sufficiently +> improbable in practice. Anyone can create a UUID and use it to identify +> something with reasonable confidence that the same identifier will never be +> unintentionally created by anyone to identify something else. Information +> labeled with UUIDs can therefore be later combined into a single database +> without needing to resolve identifier (ID) conflicts. + +Much inspiration for this library came from the [Java][javauuid] and +[Python][pyuuid] UUID libraries. + + +## Installation + +The preferred method of installation is via [Composer][]. Run the following +command to install the package and add it as a requirement to your project's +`composer.json`: + +```bash +composer require ramsey/uuid +``` + + +## Upgrading from 2.x to 3.x + +While we have made significant internal changes to the library, we have made +every effort to ensure a seamless upgrade path from the 2.x series of this +library to 3.x. + +One major breaking change is the transition from the `Rhumsaa` root namespace to +`Ramsey`. In most cases, all you will need is to change the namespace to +`Ramsey` in your code, and everything will "just work." + +Here are full details on the breaking changes to the public API of this library: + +1. All namespace references of `Rhumsaa` have changed to `Ramsey`. Simply change + the namespace to `Ramsey` in your code and everything should work. +2. The console application has moved to + [ramsey/uuid-console](https://packagist.org/packages/ramsey/uuid-console). + If using the console functionality, use Composer to require + `ramsey/uuid-console`. +3. The Doctrine field type mapping has moved to + [ramsey/uuid-doctrine](https://packagist.org/packages/ramsey/uuid-doctrine). + If using the Doctrine functionality, use Composer to require + `ramsey/uuid-doctrine`. + + +## What to do if you see a "rhumsaa/uuid is abandoned" message + +When installing your project's dependencies using Composer, you might see the +following message: + +``` +Package rhumsaa/uuid is abandoned, you should avoid using it. Use +ramsey/uuid instead. +``` + +Don't panic. Simply execute the following commands with Composer: + +``` bash +composer remove rhumsaa/uuid +composer require ramsey/uuid=^2.9 +``` + +After doing so, you will have the latest ramsey/uuid package in the 2.x series, +and there will be no need to modify any code; the namespace in the 2.x series is +still `Rhumsaa`. + + +## Requirements + +Some methods in this library have requirements due to integer size restrictions +on 32-bit and 64-bit builds of PHP. A 64-bit build of PHP and the +[Moontoast\Math][] library are recommended. However, this library is designed to +work on 32-bit builds of PHP without Moontoast\Math, with some degraded +functionality. Please check the API documentation for more information. + +If a particular requirement is not present, then an +`UnsatisfiedDependencyException` is thrown, allowing one to catch a bad call in +an environment where the call is not supported and gracefully degrade. + + +## Examples + +See the [cookbook on the wiki][wiki-cookbook] for more examples and approaches +to specific use-cases. + +```php +toString() . "\n"; // i.e. e4eaaaf2-d142-11e1-b3e4-080027620cdd + + // Generate a version 3 (name-based and hashed with MD5) UUID object + $uuid3 = Uuid::uuid3(Uuid::NAMESPACE_DNS, 'php.net'); + echo $uuid3->toString() . "\n"; // i.e. 11a38b9a-b3da-360f-9353-a5a725514269 + + // Generate a version 4 (random) UUID object + $uuid4 = Uuid::uuid4(); + echo $uuid4->toString() . "\n"; // i.e. 25769c6c-d34d-4bfe-ba98-e0ee856f3e7a + + // Generate a version 5 (name-based and hashed with SHA1) UUID object + $uuid5 = Uuid::uuid5(Uuid::NAMESPACE_DNS, 'php.net'); + echo $uuid5->toString() . "\n"; // i.e. c4a760a8-dbcf-5254-a0d9-6a4474bd1b62 + +} catch (UnsatisfiedDependencyException $e) { + + // Some dependency was not met. Either the method cannot be called on a + // 32-bit system, or it can, but it relies on Moontoast\Math to be present. + echo 'Caught exception: ' . $e->getMessage() . "\n"; + +} +``` + + +## Contributing + +Contributions are welcome! Please read [CONTRIBUTING.md][] for details. + + +## Copyright and License + +The ramsey/uuid library is copyright © [Ben Ramsey](https://benramsey.com/) and +licensed for use under the MIT License (MIT). Please see [LICENSE][] for more +information. + + +[rfc4122]: http://tools.ietf.org/html/rfc4122 +[conduct]: https://github.com/ramsey/uuid/blob/master/.github/CODE_OF_CONDUCT.md +[javauuid]: http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html +[pyuuid]: http://docs.python.org/3/library/uuid.html +[composer]: http://getcomposer.org/ +[moontoast\math]: https://packagist.org/packages/moontoast/math +[wiki-cookbook]: https://github.com/ramsey/uuid/wiki/Ramsey%5CUuid-Cookbook +[contributing.md]: https://github.com/ramsey/uuid/blob/master/.github/CONTRIBUTING.md + +[badge-source]: https://img.shields.io/badge/source-ramsey/uuid-blue.svg?style=flat-square +[badge-release]: https://img.shields.io/packagist/v/ramsey/uuid.svg?style=flat-square&label=release +[badge-license]: https://img.shields.io/packagist/l/ramsey/uuid.svg?style=flat-square +[badge-php]: https://img.shields.io/packagist/php-v/ramsey/uuid.svg?style=flat-square +[badge-build]: https://img.shields.io/travis/ramsey/uuid/master.svg?style=flat-square +[badge-coverage]: https://img.shields.io/coveralls/github/ramsey/uuid/master.svg?style=flat-square +[badge-downloads]: https://img.shields.io/packagist/dt/ramsey/uuid.svg?style=flat-square&colorB=mediumvioletred + +[source]: https://github.com/ramsey/uuid +[release]: https://packagist.org/packages/ramsey/uuid +[license]: https://github.com/ramsey/uuid/blob/master/LICENSE +[php]: https://php.net +[build]: https://travis-ci.org/ramsey/uuid +[coverage]: https://coveralls.io/github/ramsey/uuid?branch=master +[downloads]: https://packagist.org/packages/ramsey/uuid diff --git a/vendor/ramsey/uuid/composer.json b/vendor/ramsey/uuid/composer.json new file mode 100644 index 0000000..81da625 --- /dev/null +++ b/vendor/ramsey/uuid/composer.json @@ -0,0 +1,92 @@ +{ + "name": "ramsey/uuid", + "type": "library", + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "keywords": ["uuid", "identifier", "guid"], + "homepage": "https://github.com/ramsey/uuid", + "license": "MIT", + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + }, + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + } + ], + "require": { + "php": "^5.4 | ^7 | ^8", + "ext-json": "*", + "paragonie/random_compat": "^1 | ^2 | 9.99.99", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "codeception/aspect-mock": "^1 | ^2", + "doctrine/annotations": "^1.2", + "goaop/framework": "1.0.0-alpha.2 | ^1 | ^2.1", + "jakub-onderka/php-parallel-lint": "^1", + "mockery/mockery": "^0.9.11 | ^1", + "moontoast/math": "^1.1", + "paragonie/random-lib": "^2", + "php-mock/php-mock-phpunit": "^0.3 | ^1.1", + "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "ext-ctype": "Provides support for PHP Ctype functions", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter" + }, + "config": { + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Ramsey\\Uuid\\Test\\": "tests/" + } + }, + "scripts": { + "lint": "parallel-lint src tests", + "phpcs": "phpcs src tests --standard=psr2 -sp --colors", + "phpunit": "phpunit --verbose --colors=always", + "phpunit-coverage": "phpunit --verbose --colors=always --coverage-html build/coverage", + "test": [ + "@lint", + "@phpcs", + "@phpunit" + ] + }, + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "rss": "https://github.com/ramsey/uuid/releases.atom", + "source": "https://github.com/ramsey/uuid", + "wiki": "https://github.com/ramsey/uuid/wiki" + } +} diff --git a/vendor/ramsey/uuid/src/BinaryUtils.php b/vendor/ramsey/uuid/src/BinaryUtils.php new file mode 100644 index 0000000..18ea467 --- /dev/null +++ b/vendor/ramsey/uuid/src/BinaryUtils.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Builder; + +use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\Uuid; + +/** + * DefaultUuidBuilder is the default UUID builder for ramsey/uuid; it builds + * instances of Uuid objects + */ +class DefaultUuidBuilder implements UuidBuilderInterface +{ + /** + * @var NumberConverterInterface + */ + private $converter; + + /** + * Constructs the DefaultUuidBuilder + * + * @param NumberConverterInterface $converter The number converter to use when constructing the Uuid + */ + public function __construct(NumberConverterInterface $converter) + { + $this->converter = $converter; + } + + /** + * Builds a Uuid + * + * @param CodecInterface $codec The codec to use for building this Uuid + * @param array $fields An array of fields from which to construct the Uuid; + * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. + * @return Uuid + */ + public function build(CodecInterface $codec, array $fields) + { + return new Uuid($fields, $this->converter, $codec); + } +} diff --git a/vendor/ramsey/uuid/src/Builder/DegradedUuidBuilder.php b/vendor/ramsey/uuid/src/Builder/DegradedUuidBuilder.php new file mode 100644 index 0000000..7edb6de --- /dev/null +++ b/vendor/ramsey/uuid/src/Builder/DegradedUuidBuilder.php @@ -0,0 +1,53 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Builder; + +use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\DegradedUuid; + +/** + * DegradedUuidBuilder builds instances of DegradedUuid + */ +class DegradedUuidBuilder implements UuidBuilderInterface +{ + /** + * @var NumberConverterInterface + */ + private $converter; + + /** + * Constructs the DegradedUuidBuilder + * + * @param NumberConverterInterface $converter The number converter to use when constructing the DegradedUuid + */ + public function __construct(NumberConverterInterface $converter) + { + $this->converter = $converter; + } + + /** + * Builds a DegradedUuid + * + * @param CodecInterface $codec The codec to use for building this DegradedUuid + * @param array $fields An array of fields from which to construct the DegradedUuid; + * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. + * @return DegradedUuid + */ + public function build(CodecInterface $codec, array $fields) + { + return new DegradedUuid($fields, $this->converter, $codec); + } +} diff --git a/vendor/ramsey/uuid/src/Builder/UuidBuilderInterface.php b/vendor/ramsey/uuid/src/Builder/UuidBuilderInterface.php new file mode 100644 index 0000000..e4e9901 --- /dev/null +++ b/vendor/ramsey/uuid/src/Builder/UuidBuilderInterface.php @@ -0,0 +1,34 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Builder; + +use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\UuidInterface; + +/** + * UuidBuilderInterface builds instances UuidInterface + */ +interface UuidBuilderInterface +{ + /** + * Builds an instance of a UuidInterface + * + * @param CodecInterface $codec The codec to use for building this UuidInterface instance + * @param array $fields An array of fields from which to construct a UuidInterface instance; + * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. + * @return UuidInterface + */ + public function build(CodecInterface $codec, array $fields); +} diff --git a/vendor/ramsey/uuid/src/Codec/CodecInterface.php b/vendor/ramsey/uuid/src/Codec/CodecInterface.php new file mode 100644 index 0000000..c6c54c7 --- /dev/null +++ b/vendor/ramsey/uuid/src/Codec/CodecInterface.php @@ -0,0 +1,60 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Codec; + +use InvalidArgumentException; +use Ramsey\Uuid\Exception\InvalidUuidStringException; +use Ramsey\Uuid\UuidInterface; + +/** + * CodecInterface represents a UUID coder-decoder + */ +interface CodecInterface +{ + /** + * Encodes a UuidInterface as a string representation of a UUID + * + * @param UuidInterface $uuid + * @return string Hexadecimal string representation of a UUID + */ + public function encode(UuidInterface $uuid); + + /** + * Encodes a UuidInterface as a binary representation of a UUID + * + * @param UuidInterface $uuid + * @return string Binary string representation of a UUID + */ + public function encodeBinary(UuidInterface $uuid); + + /** + * Decodes a string representation of a UUID into a UuidInterface object instance + * + * @param string $encodedUuid + * @return UuidInterface + * @throws InvalidUuidStringException + */ + public function decode($encodedUuid); + + /** + * Decodes a binary representation of a UUID into a UuidInterface object instance + * + * @param string $bytes + * @return UuidInterface + * @throws InvalidUuidStringException + * @throws InvalidArgumentException if string has not 16 characters + */ + public function decodeBytes($bytes); +} diff --git a/vendor/ramsey/uuid/src/Codec/GuidStringCodec.php b/vendor/ramsey/uuid/src/Codec/GuidStringCodec.php new file mode 100644 index 0000000..3675480 --- /dev/null +++ b/vendor/ramsey/uuid/src/Codec/GuidStringCodec.php @@ -0,0 +1,103 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Codec; + +use Ramsey\Uuid\Exception\InvalidUuidStringException; +use Ramsey\Uuid\UuidInterface; + +/** + * GuidStringCodec encodes and decodes globally unique identifiers (GUID) + * + * @link https://en.wikipedia.org/wiki/Globally_unique_identifier + */ +class GuidStringCodec extends StringCodec +{ + /** + * Encodes a UuidInterface as a string representation of a GUID + * + * @param UuidInterface $uuid + * @return string Hexadecimal string representation of a GUID + */ + public function encode(UuidInterface $uuid) + { + $components = array_values($uuid->getFieldsHex()); + + // Swap byte-order on the first three fields + $this->swapFields($components); + + return vsprintf( + '%08s-%04s-%04s-%02s%02s-%012s', + $components + ); + } + + /** + * Encodes a UuidInterface as a binary representation of a GUID + * + * @param UuidInterface $uuid + * @return string Binary string representation of a GUID + */ + public function encodeBinary(UuidInterface $uuid) + { + $components = array_values($uuid->getFieldsHex()); + + return hex2bin(implode('', $components)); + } + + /** + * Decodes a string representation of a GUID into a UuidInterface object instance + * + * @param string $encodedUuid + * @return UuidInterface + * @throws InvalidUuidStringException + */ + public function decode($encodedUuid) + { + $components = $this->extractComponents($encodedUuid); + + $this->swapFields($components); + + return $this->getBuilder()->build($this, $this->getFields($components)); + } + + /** + * Decodes a binary representation of a GUID into a UuidInterface object instance + * + * @param string $bytes + * @return UuidInterface + * @throws InvalidUuidStringException + */ + public function decodeBytes($bytes) + { + // Specifically call parent::decode to preserve correct byte order + return parent::decode(bin2hex($bytes)); + } + + /** + * Swaps fields to support GUID byte order + * + * @param array $components An array of UUID components (the UUID exploded on its dashes) + * @return void + */ + protected function swapFields(array &$components) + { + $hex = unpack('H*', pack('L', hexdec($components[0]))); + $components[0] = $hex[1]; + $hex = unpack('H*', pack('S', hexdec($components[1]))); + $components[1] = $hex[1]; + $hex = unpack('H*', pack('S', hexdec($components[2]))); + $components[2] = $hex[1]; + } +} diff --git a/vendor/ramsey/uuid/src/Codec/OrderedTimeCodec.php b/vendor/ramsey/uuid/src/Codec/OrderedTimeCodec.php new file mode 100644 index 0000000..de91aab --- /dev/null +++ b/vendor/ramsey/uuid/src/Codec/OrderedTimeCodec.php @@ -0,0 +1,68 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ +namespace Ramsey\Uuid\Codec; + +use InvalidArgumentException; +use Ramsey\Uuid\UuidInterface; + +/** + * OrderedTimeCodec optimizes the bytes to increment UUIDs when time goes by, to improve database INSERTs. + * The string value will be unchanged from StringCodec. Only works for UUID type 1. + */ +class OrderedTimeCodec extends StringCodec +{ + + /** + * Encodes a UuidInterface as an optimized binary representation of a UUID + * + * @param UuidInterface $uuid + * @return string Binary string representation of a UUID + */ + public function encodeBinary(UuidInterface $uuid) + { + $fields = $uuid->getFieldsHex(); + + $optimized = [ + $fields['time_hi_and_version'], + $fields['time_mid'], + $fields['time_low'], + $fields['clock_seq_hi_and_reserved'], + $fields['clock_seq_low'], + $fields['node'], + ]; + + return hex2bin(implode('', $optimized)); + } + + /** + * Decodes an optimized binary representation of a UUID into a UuidInterface object instance + * + * @param string $bytes + * @return UuidInterface + * @throws InvalidArgumentException if string has not 16 characters + */ + public function decodeBytes($bytes) + { + if (strlen($bytes) !== 16) { + throw new InvalidArgumentException('$bytes string should contain 16 characters.'); + } + + $hex = unpack('H*', $bytes)[1]; + + // Rearrange the fields to their original order + $hex = substr($hex, 8, 4) . substr($hex, 12, 4) . substr($hex, 4, 4) . substr($hex, 0, 4) . substr($hex, 16); + + return $this->decode($hex); + } +} diff --git a/vendor/ramsey/uuid/src/Codec/StringCodec.php b/vendor/ramsey/uuid/src/Codec/StringCodec.php new file mode 100644 index 0000000..f1bc024 --- /dev/null +++ b/vendor/ramsey/uuid/src/Codec/StringCodec.php @@ -0,0 +1,170 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Codec; + +use InvalidArgumentException; +use Ramsey\Uuid\Builder\UuidBuilderInterface; +use Ramsey\Uuid\Exception\InvalidUuidStringException; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; + +/** + * StringCodec encodes and decodes RFC 4122 UUIDs + * + * @link http://tools.ietf.org/html/rfc4122 + */ +class StringCodec implements CodecInterface +{ + /** + * @var UuidBuilderInterface + */ + private $builder; + + /** + * Constructs a StringCodec for use encoding and decoding UUIDs + * + * @param UuidBuilderInterface $builder The UUID builder to use when encoding UUIDs + */ + public function __construct(UuidBuilderInterface $builder) + { + $this->builder = $builder; + } + + /** + * Encodes a UuidInterface as a string representation of a UUID + * + * @param UuidInterface $uuid + * @return string Hexadecimal string representation of a UUID + */ + public function encode(UuidInterface $uuid) + { + $fields = array_values($uuid->getFieldsHex()); + + return vsprintf( + '%08s-%04s-%04s-%02s%02s-%012s', + $fields + ); + } + + /** + * Encodes a UuidInterface as a binary representation of a UUID + * + * @param UuidInterface $uuid + * @return string Binary string representation of a UUID + */ + public function encodeBinary(UuidInterface $uuid) + { + return hex2bin($uuid->getHex()); + } + + /** + * Decodes a string representation of a UUID into a UuidInterface object instance + * + * @param string $encodedUuid + * @return UuidInterface + * @throws InvalidUuidStringException + */ + public function decode($encodedUuid) + { + $components = $this->extractComponents($encodedUuid); + $fields = $this->getFields($components); + + return $this->builder->build($this, $fields); + } + + /** + * Decodes a binary representation of a UUID into a UuidInterface object instance + * + * @param string $bytes + * @return UuidInterface + * @throws InvalidArgumentException if string has not 16 characters + */ + public function decodeBytes($bytes) + { + if (strlen($bytes) !== 16) { + throw new InvalidArgumentException('$bytes string should contain 16 characters.'); + } + + $hexUuid = unpack('H*', $bytes); + + return $this->decode($hexUuid[1]); + } + + /** + * Returns the UUID builder + * + * @return UuidBuilderInterface + */ + protected function getBuilder() + { + return $this->builder; + } + + /** + * Returns an array of UUID components (the UUID exploded on its dashes) + * + * @param string $encodedUuid + * @return array + * @throws InvalidUuidStringException + */ + protected function extractComponents($encodedUuid) + { + $nameParsed = str_replace([ + 'urn:', + 'uuid:', + '{', + '}', + '-' + ], '', $encodedUuid); + + // We have stripped out the dashes and are breaking up the string using + // substr(). In this way, we can accept a full hex value that doesn't + // contain dashes. + $components = [ + substr($nameParsed, 0, 8), + substr($nameParsed, 8, 4), + substr($nameParsed, 12, 4), + substr($nameParsed, 16, 4), + substr($nameParsed, 20) + ]; + + $nameParsed = implode('-', $components); + + if (!Uuid::isValid($nameParsed)) { + throw new InvalidUuidStringException('Invalid UUID string: ' . $encodedUuid); + } + + return $components; + } + + /** + * Returns the fields that make up this UUID + * + * @see \Ramsey\Uuid\UuidInterface::getFieldsHex() + * @param array $components + * @return array + */ + protected function getFields(array $components) + { + return [ + 'time_low' => str_pad($components[0], 8, '0', STR_PAD_LEFT), + 'time_mid' => str_pad($components[1], 4, '0', STR_PAD_LEFT), + 'time_hi_and_version' => str_pad($components[2], 4, '0', STR_PAD_LEFT), + 'clock_seq_hi_and_reserved' => str_pad(substr($components[3], 0, 2), 2, '0', STR_PAD_LEFT), + 'clock_seq_low' => str_pad(substr($components[3], 2), 2, '0', STR_PAD_LEFT), + 'node' => str_pad($components[4], 12, '0', STR_PAD_LEFT) + ]; + } +} diff --git a/vendor/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php b/vendor/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php new file mode 100644 index 0000000..270a1e7 --- /dev/null +++ b/vendor/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php @@ -0,0 +1,108 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ +namespace Ramsey\Uuid\Codec; + +use Ramsey\Uuid\Exception\InvalidUuidStringException; +use Ramsey\Uuid\UuidInterface; + +/** + * TimestampLastCombCodec encodes and decodes COMB UUIDs which have the timestamp as the first 48 bits. + * To be used with MySQL, PostgreSQL, Oracle. + */ +class TimestampFirstCombCodec extends StringCodec +{ + /** + * Encodes a UuidInterface as a string representation of a timestamp first COMB UUID + * + * @param UuidInterface $uuid + * + * @return string Hexadecimal string representation of a GUID + */ + public function encode(UuidInterface $uuid) + { + $sixPieceComponents = array_values($uuid->getFieldsHex()); + + $this->swapTimestampAndRandomBits($sixPieceComponents); + + return vsprintf( + '%08s-%04s-%04s-%02s%02s-%012s', + $sixPieceComponents + ); + } + + /** + * Encodes a UuidInterface as a binary representation of timestamp first COMB UUID + * + * @param UuidInterface $uuid + * + * @return string Binary string representation of timestamp first COMB UUID + */ + public function encodeBinary(UuidInterface $uuid) + { + $stringEncoding = $this->encode($uuid); + + return hex2bin(str_replace('-', '', $stringEncoding)); + } + + /** + * Decodes a string representation of timestamp first COMB UUID into a UuidInterface object instance + * + * @param string $encodedUuid + * + * @return UuidInterface + * @throws InvalidUuidStringException + */ + public function decode($encodedUuid) + { + $fivePieceComponents = $this->extractComponents($encodedUuid); + + $this->swapTimestampAndRandomBits($fivePieceComponents); + + return $this->getBuilder()->build($this, $this->getFields($fivePieceComponents)); + } + + /** + * Decodes a binary representation of timestamp first COMB UUID into a UuidInterface object instance + * + * @param string $bytes + * + * @return UuidInterface + * @throws InvalidUuidStringException + */ + public function decodeBytes($bytes) + { + return $this->decode(bin2hex($bytes)); + } + + /** + * Swaps the first 48 bits with the last 48 bits + * + * @param array $components An array of UUID components (the UUID exploded on its dashes) + * + * @return void + */ + protected function swapTimestampAndRandomBits(array &$components) + { + $last48Bits = $components[4]; + if (count($components) == 6) { + $last48Bits = $components[5]; + $components[5] = $components[0] . $components[1]; + } else { + $components[4] = $components[0] . $components[1]; + } + + $components[0] = substr($last48Bits, 0, 8); + $components[1] = substr($last48Bits, 8, 4); + } +} diff --git a/vendor/ramsey/uuid/src/Codec/TimestampLastCombCodec.php b/vendor/ramsey/uuid/src/Codec/TimestampLastCombCodec.php new file mode 100644 index 0000000..240f613 --- /dev/null +++ b/vendor/ramsey/uuid/src/Codec/TimestampLastCombCodec.php @@ -0,0 +1,22 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ +namespace Ramsey\Uuid\Codec; + +/** + * TimestampLastCombCodec encodes and decodes COMB UUIDs which have the timestamp as the last 48 bits. + * To be used with MSSQL. + */ +class TimestampLastCombCodec extends StringCodec +{ +} diff --git a/vendor/ramsey/uuid/src/Converter/Number/BigNumberConverter.php b/vendor/ramsey/uuid/src/Converter/Number/BigNumberConverter.php new file mode 100644 index 0000000..d235122 --- /dev/null +++ b/vendor/ramsey/uuid/src/Converter/Number/BigNumberConverter.php @@ -0,0 +1,54 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter\Number; + +use Moontoast\Math\BigNumber; +use Ramsey\Uuid\Converter\NumberConverterInterface; + +/** + * BigNumberConverter converts UUIDs from hexadecimal characters into + * moontoast/math `BigNumber` representations of integers and vice versa + */ +class BigNumberConverter implements NumberConverterInterface +{ + /** + * Converts a hexadecimal number into a `Moontoast\Math\BigNumber` representation + * + * @param string $hex The hexadecimal string representation to convert + * @return BigNumber + */ + public function fromHex($hex) + { + $number = BigNumber::convertToBase10($hex, 16); + + return new BigNumber($number); + } + + /** + * Converts an integer or `Moontoast\Math\BigNumber` integer representation + * into a hexadecimal string representation + * + * @param int|string|BigNumber $integer An integer or `Moontoast\Math\BigNumber` + * @return string Hexadecimal string + */ + public function toHex($integer) + { + if (!$integer instanceof BigNumber) { + $integer = new BigNumber($integer); + } + + return BigNumber::convertFromBase10($integer, 16); + } +} diff --git a/vendor/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php b/vendor/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php new file mode 100644 index 0000000..96a011c --- /dev/null +++ b/vendor/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php @@ -0,0 +1,58 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter\Number; + +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; +use Ramsey\Uuid\Converter\NumberConverterInterface; + +/** + * DegradedNumberConverter throws `UnsatisfiedDependencyException` exceptions + * if attempting to use number conversion functionality in an environment that + * does not support large integers (i.e. when moontoast/math is not available) + */ +class DegradedNumberConverter implements NumberConverterInterface +{ + /** + * Throws an `UnsatisfiedDependencyException` + * + * @param string $hex The hexadecimal string representation to convert + * @return void + * @throws UnsatisfiedDependencyException + */ + public function fromHex($hex) + { + throw new UnsatisfiedDependencyException( + 'Cannot call ' . __METHOD__ . ' without support for large ' + . 'integers, since integer is an unsigned ' + . '128-bit integer; Moontoast\Math\BigNumber is required.' + ); + } + + /** + * Throws an `UnsatisfiedDependencyException` + * + * @param mixed $integer An integer representation to convert + * @return void + * @throws UnsatisfiedDependencyException + */ + public function toHex($integer) + { + throw new UnsatisfiedDependencyException( + 'Cannot call ' . __METHOD__ . ' without support for large ' + . 'integers, since integer is an unsigned ' + . '128-bit integer; Moontoast\Math\BigNumber is required. ' + ); + } +} diff --git a/vendor/ramsey/uuid/src/Converter/NumberConverterInterface.php b/vendor/ramsey/uuid/src/Converter/NumberConverterInterface.php new file mode 100644 index 0000000..b978e2e --- /dev/null +++ b/vendor/ramsey/uuid/src/Converter/NumberConverterInterface.php @@ -0,0 +1,48 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter; + +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; + +/** + * NumberConverterInterface converts UUIDs from hexadecimal characters into + * representations of integers and vice versa + */ +interface NumberConverterInterface +{ + /** + * Converts a hexadecimal number into an integer representation of the number + * + * The integer representation returned may be an object or a string + * representation of the integer, depending on the implementation. + * + * @param string $hex The hexadecimal string representation to convert + * @return mixed + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + */ + public function fromHex($hex); + + /** + * Converts an integer representation into a hexadecimal string representation + * of the number + * + * @param mixed $integer An integer representation to convert; this may be + * a true integer, a string integer, or a object representation that + * this converter can understand + * @return string Hexadecimal string + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + */ + public function toHex($integer); +} diff --git a/vendor/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php b/vendor/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php new file mode 100644 index 0000000..112f722 --- /dev/null +++ b/vendor/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php @@ -0,0 +1,59 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter\Time; + +use Moontoast\Math\BigNumber; +use Ramsey\Uuid\Converter\TimeConverterInterface; + +/** + * BigNumberTimeConverter uses the moontoast/math library's `BigNumber` to + * provide facilities for converting parts of time into representations that may + * be used in UUIDs + */ +class BigNumberTimeConverter implements TimeConverterInterface +{ + /** + * Uses the provided seconds and micro-seconds to calculate the time_low, + * time_mid, and time_high fields used by RFC 4122 version 1 UUIDs + * + * @param string $seconds + * @param string $microSeconds + * @return string[] An array containing `low`, `mid`, and `high` keys + * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 + */ + public function calculateTime($seconds, $microSeconds) + { + $uuidTime = new BigNumber('0'); + + $sec = new BigNumber($seconds); + $sec->multiply('10000000'); + + $usec = new BigNumber($microSeconds); + $usec->multiply('10'); + + $uuidTime + ->add($sec) + ->add($usec) + ->add('122192928000000000'); + + $uuidTimeHex = sprintf('%016s', $uuidTime->convertToBase(16)); + + return [ + 'low' => substr($uuidTimeHex, 8), + 'mid' => substr($uuidTimeHex, 4, 4), + 'hi' => substr($uuidTimeHex, 0, 4), + ]; + } +} diff --git a/vendor/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php b/vendor/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php new file mode 100644 index 0000000..b94589c --- /dev/null +++ b/vendor/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php @@ -0,0 +1,42 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter\Time; + +use Ramsey\Uuid\Converter\TimeConverterInterface; +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; + +/** + * DegradedTimeConverter throws `UnsatisfiedDependencyException` exceptions + * if attempting to use time conversion functionality in an environment that + * does not support large integers (i.e. when moontoast/math is not available) + */ +class DegradedTimeConverter implements TimeConverterInterface +{ + /** + * Throws an `UnsatisfiedDependencyException` + * + * @param string $seconds + * @param string $microSeconds + * @return void + * @throws UnsatisfiedDependencyException if called on a 32-bit system and `Moontoast\Math\BigNumber` is not present + */ + public function calculateTime($seconds, $microSeconds) + { + throw new UnsatisfiedDependencyException( + 'When calling ' . __METHOD__ . ' on a 32-bit system, ' + . 'Moontoast\Math\BigNumber must be present.' + ); + } +} diff --git a/vendor/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php b/vendor/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php new file mode 100644 index 0000000..57c882d --- /dev/null +++ b/vendor/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php @@ -0,0 +1,47 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter\Time; + +use Ramsey\Uuid\Converter\TimeConverterInterface; + +/** + * PhpTimeConverter uses built-in PHP functions and standard math operations + * available to the PHP programming language to provide facilities for + * converting parts of time into representations that may be used in UUIDs + */ +class PhpTimeConverter implements TimeConverterInterface +{ + /** + * Uses the provided seconds and micro-seconds to calculate the time_low, + * time_mid, and time_high fields used by RFC 4122 version 1 UUIDs + * + * @param string $seconds + * @param string $microSeconds + * @return string[] An array containing `low`, `mid`, and `high` keys + * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 + */ + public function calculateTime($seconds, $microSeconds) + { + // 0x01b21dd213814000 is the number of 100-ns intervals between the + // UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. + $uuidTime = ($seconds * 10000000) + ($microSeconds * 10) + 0x01b21dd213814000; + + return [ + 'low' => sprintf('%08x', $uuidTime & 0xffffffff), + 'mid' => sprintf('%04x', ($uuidTime >> 32) & 0xffff), + 'hi' => sprintf('%04x', ($uuidTime >> 48) & 0x0fff), + ]; + } +} diff --git a/vendor/ramsey/uuid/src/Converter/TimeConverterInterface.php b/vendor/ramsey/uuid/src/Converter/TimeConverterInterface.php new file mode 100644 index 0000000..c851792 --- /dev/null +++ b/vendor/ramsey/uuid/src/Converter/TimeConverterInterface.php @@ -0,0 +1,37 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Converter; + +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; + +/** + * TimeConverterInterface provides facilities for converting parts of time into + * representations that may be used in UUIDs + */ +interface TimeConverterInterface +{ + /** + * Uses the provided seconds and micro-seconds to calculate the time_low, + * time_mid, and time_high fields used by RFC 4122 version 1 UUIDs + * + * @param string $seconds + * @param string $microSeconds + * @return string[] An array guaranteed to contain `low`, `mid`, and `high` keys + * @throws UnsatisfiedDependencyException if called on a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 + */ + public function calculateTime($seconds, $microSeconds); +} diff --git a/vendor/ramsey/uuid/src/DegradedUuid.php b/vendor/ramsey/uuid/src/DegradedUuid.php new file mode 100644 index 0000000..2669761 --- /dev/null +++ b/vendor/ramsey/uuid/src/DegradedUuid.php @@ -0,0 +1,116 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid; + +use DateTime; +use Moontoast\Math\BigNumber; +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; +use Ramsey\Uuid\Exception\UnsupportedOperationException; + +/** + * DegradedUuid represents an RFC 4122 UUID on 32-bit systems + * + * @see Uuid + */ +class DegradedUuid extends Uuid +{ + /** + * @inheritdoc + */ + public function getDateTime() + { + if ($this->getVersion() != 1) { + throw new UnsupportedOperationException('Not a time-based UUID'); + } + + $time = $this->converter->fromHex($this->getTimestampHex()); + + $ts = new BigNumber($time, 20); + $ts->subtract('122192928000000000'); + $ts->divide('10000000.0'); + $ts->round(); + $unixTime = $ts->getValue(); + + return new DateTime("@{$unixTime}"); + } + + /** + * For degraded UUIDs, throws an `UnsatisfiedDependencyException` when + * called on a 32-bit system + * + * @throws UnsatisfiedDependencyException if called on a 32-bit system + */ + public function getFields() + { + throw new UnsatisfiedDependencyException( + 'Cannot call ' . __METHOD__ . ' on a 32-bit system, since some ' + . 'values overflow the system max integer value' + . '; consider calling getFieldsHex instead' + ); + } + + /** + * For degraded UUIDs, throws an `UnsatisfiedDependencyException` when + * called on a 32-bit system + * + * @throws UnsatisfiedDependencyException if called on a 32-bit system + */ + public function getNode() + { + throw new UnsatisfiedDependencyException( + 'Cannot call ' . __METHOD__ . ' on a 32-bit system, since node ' + . 'is an unsigned 48-bit integer and can overflow the system ' + . 'max integer value' + . '; consider calling getNodeHex instead' + ); + } + + /** + * For degraded UUIDs, throws an `UnsatisfiedDependencyException` when + * called on a 32-bit system + * + * @throws UnsatisfiedDependencyException if called on a 32-bit system + */ + public function getTimeLow() + { + throw new UnsatisfiedDependencyException( + 'Cannot call ' . __METHOD__ . ' on a 32-bit system, since time_low ' + . 'is an unsigned 32-bit integer and can overflow the system ' + . 'max integer value' + . '; consider calling getTimeLowHex instead' + ); + } + + /** + * For degraded UUIDs, throws an `UnsatisfiedDependencyException` when + * called on a 32-bit system + * + * @throws UnsatisfiedDependencyException if called on a 32-bit system + * @throws UnsupportedOperationException If this UUID is not a version 1 UUID + */ + public function getTimestamp() + { + if ($this->getVersion() != 1) { + throw new UnsupportedOperationException('Not a time-based UUID'); + } + + throw new UnsatisfiedDependencyException( + 'Cannot call ' . __METHOD__ . ' on a 32-bit system, since timestamp ' + . 'is an unsigned 60-bit integer and can overflow the system ' + . 'max integer value' + . '; consider calling getTimestampHex instead' + ); + } +} diff --git a/vendor/ramsey/uuid/src/Exception/InvalidUuidStringException.php b/vendor/ramsey/uuid/src/Exception/InvalidUuidStringException.php new file mode 100644 index 0000000..7df0e8c --- /dev/null +++ b/vendor/ramsey/uuid/src/Exception/InvalidUuidStringException.php @@ -0,0 +1,24 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Exception; + +use InvalidArgumentException; + +/** + * Thrown to indicate that the parsed UUID string is invalid. + */ +class InvalidUuidStringException extends InvalidArgumentException +{ +} diff --git a/vendor/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php b/vendor/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php new file mode 100644 index 0000000..89c7396 --- /dev/null +++ b/vendor/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php @@ -0,0 +1,25 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Exception; + +use RuntimeException; + +/** + * Thrown to indicate that the requested operation has dependencies that have not + * been satisfied. + */ +class UnsatisfiedDependencyException extends RuntimeException +{ +} diff --git a/vendor/ramsey/uuid/src/Exception/UnsupportedOperationException.php b/vendor/ramsey/uuid/src/Exception/UnsupportedOperationException.php new file mode 100644 index 0000000..4340947 --- /dev/null +++ b/vendor/ramsey/uuid/src/Exception/UnsupportedOperationException.php @@ -0,0 +1,24 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Exception; + +use RuntimeException; + +/** + * Thrown to indicate that the requested operation is not supported. + */ +class UnsupportedOperationException extends RuntimeException +{ +} diff --git a/vendor/ramsey/uuid/src/FeatureSet.php b/vendor/ramsey/uuid/src/FeatureSet.php new file mode 100644 index 0000000..2027b9e --- /dev/null +++ b/vendor/ramsey/uuid/src/FeatureSet.php @@ -0,0 +1,335 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid; + +use Ramsey\Uuid\Converter\TimeConverterInterface; +use Ramsey\Uuid\Generator\PeclUuidTimeGenerator; +use Ramsey\Uuid\Provider\Node\FallbackNodeProvider; +use Ramsey\Uuid\Provider\Node\RandomNodeProvider; +use Ramsey\Uuid\Provider\Node\SystemNodeProvider; +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\Converter\Number\BigNumberConverter; +use Ramsey\Uuid\Converter\Number\DegradedNumberConverter; +use Ramsey\Uuid\Converter\Time\BigNumberTimeConverter; +use Ramsey\Uuid\Converter\Time\DegradedTimeConverter; +use Ramsey\Uuid\Converter\Time\PhpTimeConverter; +use Ramsey\Uuid\Provider\Time\SystemTimeProvider; +use Ramsey\Uuid\Builder\UuidBuilderInterface; +use Ramsey\Uuid\Builder\DefaultUuidBuilder; +use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\Codec\StringCodec; +use Ramsey\Uuid\Codec\GuidStringCodec; +use Ramsey\Uuid\Builder\DegradedUuidBuilder; +use Ramsey\Uuid\Generator\RandomGeneratorFactory; +use Ramsey\Uuid\Generator\RandomGeneratorInterface; +use Ramsey\Uuid\Generator\TimeGeneratorFactory; +use Ramsey\Uuid\Generator\TimeGeneratorInterface; +use Ramsey\Uuid\Provider\TimeProviderInterface; +use Ramsey\Uuid\Provider\NodeProviderInterface; + +/** + * FeatureSet detects and exposes available features in the current environment + * (32- or 64-bit, available dependencies, etc.) + */ +class FeatureSet +{ + /** + * @var bool + */ + private $disableBigNumber = false; + + /** + * @var bool + */ + private $disable64Bit = false; + + /** + * @var bool + */ + private $ignoreSystemNode = false; + + /** + * @var bool + */ + private $enablePecl = false; + + /** + * @var UuidBuilderInterface + */ + private $builder; + + /** + * @var CodecInterface + */ + private $codec; + + /** + * @var NodeProviderInterface + */ + private $nodeProvider; + + /** + * @var NumberConverterInterface + */ + private $numberConverter; + + /** + * @var RandomGeneratorInterface + */ + private $randomGenerator; + + /** + * @var TimeGeneratorInterface + */ + private $timeGenerator; + + /** + * Constructs a `FeatureSet` for use by a `UuidFactory` to determine or set + * features available to the environment + * + * @param bool $useGuids Whether to build UUIDs using the `GuidStringCodec` + * @param bool $force32Bit Whether to force the use of 32-bit functionality + * (primarily for testing purposes) + * @param bool $forceNoBigNumber Whether to disable the use of moontoast/math + * `BigNumber` (primarily for testing purposes) + * @param bool $ignoreSystemNode Whether to disable attempts to check for + * the system host ID (primarily for testing purposes) + * @param bool $enablePecl Whether to enable the use of the `PeclUuidTimeGenerator` + * to generate version 1 UUIDs + */ + public function __construct( + $useGuids = false, + $force32Bit = false, + $forceNoBigNumber = false, + $ignoreSystemNode = false, + $enablePecl = false + ) { + $this->disableBigNumber = $forceNoBigNumber; + $this->disable64Bit = $force32Bit; + $this->ignoreSystemNode = $ignoreSystemNode; + $this->enablePecl = $enablePecl; + + $this->numberConverter = $this->buildNumberConverter(); + $this->builder = $this->buildUuidBuilder(); + $this->codec = $this->buildCodec($useGuids); + $this->nodeProvider = $this->buildNodeProvider(); + $this->randomGenerator = $this->buildRandomGenerator(); + $this->setTimeProvider(new SystemTimeProvider()); + } + + /** + * Returns the builder configured for this environment + * + * @return UuidBuilderInterface + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * Returns the UUID UUID coder-decoder configured for this environment + * + * @return CodecInterface + */ + public function getCodec() + { + return $this->codec; + } + + /** + * Returns the system node ID provider configured for this environment + * + * @return NodeProviderInterface + */ + public function getNodeProvider() + { + return $this->nodeProvider; + } + + /** + * Returns the number converter configured for this environment + * + * @return NumberConverterInterface + */ + public function getNumberConverter() + { + return $this->numberConverter; + } + + /** + * Returns the random UUID generator configured for this environment + * + * @return RandomGeneratorInterface + */ + public function getRandomGenerator() + { + return $this->randomGenerator; + } + + /** + * Returns the time-based UUID generator configured for this environment + * + * @return TimeGeneratorInterface + */ + public function getTimeGenerator() + { + return $this->timeGenerator; + } + + /** + * Sets the time provider for use in this environment + * + * @param TimeProviderInterface $timeProvider + */ + public function setTimeProvider(TimeProviderInterface $timeProvider) + { + $this->timeGenerator = $this->buildTimeGenerator($timeProvider); + } + + /** + * Determines which UUID coder-decoder to use and returns the configured + * codec for this environment + * + * @param bool $useGuids Whether to build UUIDs using the `GuidStringCodec` + * @return CodecInterface + */ + protected function buildCodec($useGuids = false) + { + if ($useGuids) { + return new GuidStringCodec($this->builder); + } + + return new StringCodec($this->builder); + } + + /** + * Determines which system node ID provider to use and returns the configured + * system node ID provider for this environment + * + * @return NodeProviderInterface + */ + protected function buildNodeProvider() + { + if ($this->ignoreSystemNode) { + return new RandomNodeProvider(); + } + + return new FallbackNodeProvider([ + new SystemNodeProvider(), + new RandomNodeProvider() + ]); + } + + /** + * Determines which number converter to use and returns the configured + * number converter for this environment + * + * @return NumberConverterInterface + */ + protected function buildNumberConverter() + { + if ($this->hasBigNumber()) { + return new BigNumberConverter(); + } + + return new DegradedNumberConverter(); + } + + /** + * Determines which random UUID generator to use and returns the configured + * random UUID generator for this environment + * + * @return RandomGeneratorInterface + */ + protected function buildRandomGenerator() + { + return (new RandomGeneratorFactory())->getGenerator(); + } + + /** + * Determines which time-based UUID generator to use and returns the configured + * time-based UUID generator for this environment + * + * @param TimeProviderInterface $timeProvider + * @return TimeGeneratorInterface + */ + protected function buildTimeGenerator(TimeProviderInterface $timeProvider) + { + if ($this->enablePecl) { + return new PeclUuidTimeGenerator(); + } + + return (new TimeGeneratorFactory( + $this->nodeProvider, + $this->buildTimeConverter(), + $timeProvider + ))->getGenerator(); + } + + /** + * Determines which time converter to use and returns the configured + * time converter for this environment + * + * @return TimeConverterInterface + */ + protected function buildTimeConverter() + { + if ($this->is64BitSystem()) { + return new PhpTimeConverter(); + } + + if ($this->hasBigNumber()) { + return new BigNumberTimeConverter(); + } + + return new DegradedTimeConverter(); + } + + /** + * Determines which UUID builder to use and returns the configured UUID + * builder for this environment + * + * @return UuidBuilderInterface + */ + protected function buildUuidBuilder() + { + if ($this->is64BitSystem()) { + return new DefaultUuidBuilder($this->numberConverter); + } + + return new DegradedUuidBuilder($this->numberConverter); + } + + /** + * Returns true if the system has `Moontoast\Math\BigNumber` + * + * @return bool + */ + protected function hasBigNumber() + { + return class_exists('Moontoast\Math\BigNumber') && !$this->disableBigNumber; + } + + /** + * Returns true if the system is 64-bit, false otherwise + * + * @return bool + */ + protected function is64BitSystem() + { + return PHP_INT_SIZE == 8 && !$this->disable64Bit; + } +} diff --git a/vendor/ramsey/uuid/src/Generator/CombGenerator.php b/vendor/ramsey/uuid/src/Generator/CombGenerator.php new file mode 100644 index 0000000..1d4a5f6 --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/CombGenerator.php @@ -0,0 +1,91 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use Exception; +use InvalidArgumentException; +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; + +/** + * CombGenerator provides functionality to generate COMB (combined GUID/timestamp) + * sequential UUIDs + * + * @link https://en.wikipedia.org/wiki/Globally_unique_identifier#Sequential_algorithms + */ +class CombGenerator implements RandomGeneratorInterface +{ + const TIMESTAMP_BYTES = 6; + + /** + * @var RandomGeneratorInterface + */ + private $randomGenerator; + + /** + * @var NumberConverterInterface + */ + private $converter; + + /** + * Constructs a `CombGenerator` using a random-number generator and a number converter + * + * @param RandomGeneratorInterface $generator Random-number generator for the non-time part. + * @param NumberConverterInterface $numberConverter Instance of number converter. + */ + public function __construct(RandomGeneratorInterface $generator, NumberConverterInterface $numberConverter) + { + $this->converter = $numberConverter; + $this->randomGenerator = $generator; + } + + /** + * Generates a string of binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws InvalidArgumentException if length is not a positive integer + * @throws Exception + */ + public function generate($length) + { + if ($length < self::TIMESTAMP_BYTES || $length < 0) { + throw new InvalidArgumentException('Length must be a positive integer.'); + } + + $hash = ''; + + if (self::TIMESTAMP_BYTES > 0 && $length > self::TIMESTAMP_BYTES) { + $hash = $this->randomGenerator->generate($length - self::TIMESTAMP_BYTES); + } + + $lsbTime = str_pad($this->converter->toHex($this->timestamp()), self::TIMESTAMP_BYTES * 2, '0', STR_PAD_LEFT); + + return hex2bin(str_pad(bin2hex($hash), $length - self::TIMESTAMP_BYTES, '0') . $lsbTime); + } + + /** + * Returns current timestamp as integer, precise to 0.00001 seconds + * + * @return string + */ + private function timestamp() + { + $time = explode(' ', microtime(false)); + + return $time[1] . substr($time[0], 2, 5); + } +} diff --git a/vendor/ramsey/uuid/src/Generator/DefaultTimeGenerator.php b/vendor/ramsey/uuid/src/Generator/DefaultTimeGenerator.php new file mode 100644 index 0000000..5c5ccb2 --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/DefaultTimeGenerator.php @@ -0,0 +1,141 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use Exception; +use InvalidArgumentException; +use Ramsey\Uuid\BinaryUtils; +use Ramsey\Uuid\Converter\TimeConverterInterface; +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; +use Ramsey\Uuid\Provider\NodeProviderInterface; +use Ramsey\Uuid\Provider\TimeProviderInterface; + +/** + * DefaultTimeGenerator provides functionality to generate strings of binary + * data for version 1 UUIDs based on a host ID, sequence number, and the current + * time + */ +class DefaultTimeGenerator implements TimeGeneratorInterface +{ + /** + * @var NodeProviderInterface + */ + private $nodeProvider; + + /** + * @var TimeConverterInterface + */ + private $timeConverter; + + /** + * @var TimeProviderInterface + */ + private $timeProvider; + + /** + * Constructs a `DefaultTimeGenerator` using a node provider, time converter, + * and time provider + * + * @param NodeProviderInterface $nodeProvider + * @param TimeConverterInterface $timeConverter + * @param TimeProviderInterface $timeProvider + */ + public function __construct( + NodeProviderInterface $nodeProvider, + TimeConverterInterface $timeConverter, + TimeProviderInterface $timeProvider + ) { + $this->nodeProvider = $nodeProvider; + $this->timeConverter = $timeConverter; + $this->timeProvider = $timeProvider; + } + + /** + * Generate a version 1 UUID from a host ID, sequence number, and the current time + * + * If $node is not given, we will attempt to obtain the local hardware + * address. If $clockSeq is given, it is used as the sequence number; + * otherwise a random 14-bit sequence number is chosen. + * + * @param int|string $node A 48-bit number representing the hardware address + * This number may be represented as an integer or a hexadecimal string. + * @param int $clockSeq A 14-bit number used to help avoid duplicates that + * could arise when the clock is set backwards in time or if the node ID + * changes. + * @return string A binary string + * @throws UnsatisfiedDependencyException if called on a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + * @throws InvalidArgumentException + * @throws Exception if it was not possible to gather sufficient entropy + */ + public function generate($node = null, $clockSeq = null) + { + $node = $this->getValidNode($node); + + if ($clockSeq === null) { + // Not using "stable storage"; see RFC 4122, Section 4.2.1.1 + $clockSeq = random_int(0, 0x3fff); + } + + // Create a 60-bit time value as a count of 100-nanosecond intervals + // since 00:00:00.00, 15 October 1582 + $timeOfDay = $this->timeProvider->currentTime(); + $uuidTime = $this->timeConverter->calculateTime($timeOfDay['sec'], $timeOfDay['usec']); + + $timeHi = BinaryUtils::applyVersion($uuidTime['hi'], 1); + $clockSeqHi = BinaryUtils::applyVariant($clockSeq >> 8); + + $hex = vsprintf( + '%08s%04s%04s%02s%02s%012s', + [ + $uuidTime['low'], + $uuidTime['mid'], + sprintf('%04x', $timeHi), + sprintf('%02x', $clockSeqHi), + sprintf('%02x', $clockSeq & 0xff), + $node, + ] + ); + + return hex2bin($hex); + } + + /** + * Uses the node provider given when constructing this instance to get + * the node ID (usually a MAC address) + * + * @param string|int $node A node value that may be used to override the node provider + * @return string Hexadecimal representation of the node ID + * @throws InvalidArgumentException + * @throws Exception + */ + protected function getValidNode($node) + { + if ($node === null) { + $node = $this->nodeProvider->getNode(); + } + + // Convert the node to hex, if it is still an integer + if (is_int($node)) { + $node = sprintf('%012x', $node); + } + + if (!ctype_xdigit($node) || strlen($node) > 12) { + throw new InvalidArgumentException('Invalid node value'); + } + + return strtolower(sprintf('%012s', $node)); + } +} diff --git a/vendor/ramsey/uuid/src/Generator/MtRandGenerator.php b/vendor/ramsey/uuid/src/Generator/MtRandGenerator.php new file mode 100644 index 0000000..8d4b5f9 --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/MtRandGenerator.php @@ -0,0 +1,45 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * MtRandRandomGenerator provides functionality to generate strings of random + * binary data using the `mt_rand()` PHP function + * + * @deprecated The mt_rand() function is not a reliable source of randomness. + * The default RandomBytesGenerator, which uses the random_bytes() function, + * is recommended as the safest and most reliable source of randomness. + * This generator will be removed in ramsey/uuid 4.0.0. + * @link http://php.net/mt_rand + */ +class MtRandGenerator implements RandomGeneratorInterface +{ + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + */ + public function generate($length) + { + $bytes = ''; + + for ($i = 1; $i <= $length; $i++) { + $bytes = chr(mt_rand(0, 255)) . $bytes; + } + + return $bytes; + } +} diff --git a/vendor/ramsey/uuid/src/Generator/OpenSslGenerator.php b/vendor/ramsey/uuid/src/Generator/OpenSslGenerator.php new file mode 100644 index 0000000..47abf9b --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/OpenSslGenerator.php @@ -0,0 +1,43 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * OpenSslRandomGenerator provides functionality to generate strings of random + * binary data using the `openssl_random_pseudo_bytes()` PHP function + * + * The use of this generator requires PHP to be compiled using the + * `--with-openssl` option. + * + * @deprecated The openssl_random_pseudo_bytes() function is not a reliable + * source of randomness. The default RandomBytesGenerator, which uses the + * random_bytes() function, is recommended as the safest and most reliable + * source of randomness. + * This generator will be removed in ramsey/uuid 4.0.0. + * @link http://php.net/openssl_random_pseudo_bytes + */ +class OpenSslGenerator implements RandomGeneratorInterface +{ + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + */ + public function generate($length) + { + return openssl_random_pseudo_bytes($length); + } +} diff --git a/vendor/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php b/vendor/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php new file mode 100644 index 0000000..fc2ef7e --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php @@ -0,0 +1,37 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * PeclUuidRandomGenerator provides functionality to generate strings of random + * binary data using the PECL UUID PHP extension + * + * @link https://pecl.php.net/package/uuid + */ +class PeclUuidRandomGenerator implements RandomGeneratorInterface +{ + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + */ + public function generate($length) + { + $uuid = uuid_create(UUID_TYPE_RANDOM); + + return uuid_parse($uuid); + } +} diff --git a/vendor/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php b/vendor/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php new file mode 100644 index 0000000..7ccf16f --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php @@ -0,0 +1,38 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * PeclUuidTimeGenerator provides functionality to generate strings of binary + * data for version 1 UUIDs using the PECL UUID PHP extension + * + * @link https://pecl.php.net/package/uuid + */ +class PeclUuidTimeGenerator implements TimeGeneratorInterface +{ + /** + * Generate a version 1 UUID using the PECL UUID extension + * + * @param int|string $node Not used in this context + * @param int $clockSeq Not used in this context + * @return string A binary string + */ + public function generate($node = null, $clockSeq = null) + { + $uuid = uuid_create(UUID_TYPE_TIME); + + return uuid_parse($uuid); + } +} diff --git a/vendor/ramsey/uuid/src/Generator/RandomBytesGenerator.php b/vendor/ramsey/uuid/src/Generator/RandomBytesGenerator.php new file mode 100644 index 0000000..cc3d379 --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/RandomBytesGenerator.php @@ -0,0 +1,39 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use Exception; + +/** + * RandomBytesGenerator provides functionality to generate strings of random + * binary data using `random_bytes()` function in PHP 7+ or paragonie/random_compat + * + * @link http://php.net/random_bytes + * @link https://github.com/paragonie/random_compat + */ +class RandomBytesGenerator implements RandomGeneratorInterface +{ + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + * @throws Exception if it was not possible to gather sufficient entropy + */ + public function generate($length) + { + return random_bytes($length); + } +} diff --git a/vendor/ramsey/uuid/src/Generator/RandomGeneratorFactory.php b/vendor/ramsey/uuid/src/Generator/RandomGeneratorFactory.php new file mode 100644 index 0000000..3911062 --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/RandomGeneratorFactory.php @@ -0,0 +1,31 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * A factory for retrieving a random generator, based on the environment + */ +class RandomGeneratorFactory +{ + /** + * Returns a default random generator, based on the current environment + * + * @return RandomGeneratorInterface + */ + public static function getGenerator() + { + return new RandomBytesGenerator(); + } +} diff --git a/vendor/ramsey/uuid/src/Generator/RandomGeneratorInterface.php b/vendor/ramsey/uuid/src/Generator/RandomGeneratorInterface.php new file mode 100644 index 0000000..b791d60 --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/RandomGeneratorInterface.php @@ -0,0 +1,37 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use Exception; +use InvalidArgumentException; +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; + +/** + * RandomGeneratorInterface provides functionality to generate strings of random + * binary data + */ +interface RandomGeneratorInterface +{ + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws InvalidArgumentException + * @throws Exception if it was not possible to gather sufficient entropy + */ + public function generate($length); +} diff --git a/vendor/ramsey/uuid/src/Generator/RandomLibAdapter.php b/vendor/ramsey/uuid/src/Generator/RandomLibAdapter.php new file mode 100644 index 0000000..5aa0e88 --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/RandomLibAdapter.php @@ -0,0 +1,62 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use RandomLib\Generator; +use RandomLib\Factory; + +/** + * RandomLibAdapter provides functionality to generate strings of random + * binary data using the paragonie/random-lib library + * + * @link https://packagist.org/packages/paragonie/random-lib + */ +class RandomLibAdapter implements RandomGeneratorInterface +{ + /** + * @var Generator + */ + private $generator; + + /** + * Constructs a `RandomLibAdapter` using a `RandomLib\Generator` + * + * By default, if no `Generator` is passed in, this creates a high-strength + * generator to use when generating random binary data. + * + * @param Generator $generator An paragonie/random-lib `Generator` + */ + public function __construct(Generator $generator = null) + { + $this->generator = $generator; + + if ($this->generator === null) { + $factory = new Factory(); + + $this->generator = $factory->getHighStrengthGenerator(); + } + } + + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + */ + public function generate($length) + { + return $this->generator->generate($length); + } +} diff --git a/vendor/ramsey/uuid/src/Generator/SodiumRandomGenerator.php b/vendor/ramsey/uuid/src/Generator/SodiumRandomGenerator.php new file mode 100644 index 0000000..f4ccf85 --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/SodiumRandomGenerator.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +/** + * SodiumRandomGenerator provides functionality to generate strings of random + * binary data using the PECL libsodium extension + * + * @deprecated As of PHP 7.2.0, the libsodium extension is bundled with PHP, and + * the random_bytes() PHP function is now the recommended method for + * generating random byes. The default RandomBytesGenerator uses the + * random_bytes() function. + * This generator will be removed in ramsey/uuid 4.0.0. + * @link http://pecl.php.net/package/libsodium + * @link https://paragonie.com/book/pecl-libsodium + */ +class SodiumRandomGenerator implements RandomGeneratorInterface +{ + /** + * Generates a string of random binary data of the specified length + * + * @param integer $length The number of bytes of random binary data to generate + * @return string A binary string + */ + public function generate($length) + { + return \Sodium\randombytes_buf($length); + } +} diff --git a/vendor/ramsey/uuid/src/Generator/TimeGeneratorFactory.php b/vendor/ramsey/uuid/src/Generator/TimeGeneratorFactory.php new file mode 100644 index 0000000..24d501b --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/TimeGeneratorFactory.php @@ -0,0 +1,72 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use Ramsey\Uuid\Converter\TimeConverterInterface; +use Ramsey\Uuid\Provider\NodeProviderInterface; +use Ramsey\Uuid\Provider\TimeProviderInterface; + +/** + * A factory for retrieving a time generator, based on the environment + */ +class TimeGeneratorFactory +{ + /** + * @var NodeProviderInterface + */ + private $nodeProvider; + + /** + * @var TimeConverterInterface + */ + private $timeConverter; + + /** + * @var TimeProviderInterface + */ + private $timeProvider; + + /** + * Constructs a `TimeGeneratorFactory` using a node provider, time converter, + * and time provider + * + * @param NodeProviderInterface $nodeProvider + * @param TimeConverterInterface $timeConverter + * @param TimeProviderInterface $timeProvider + */ + public function __construct( + NodeProviderInterface $nodeProvider, + TimeConverterInterface $timeConverter, + TimeProviderInterface $timeProvider + ) { + $this->nodeProvider = $nodeProvider; + $this->timeConverter = $timeConverter; + $this->timeProvider = $timeProvider; + } + + /** + * Returns a default time generator, based on the current environment + * + * @return TimeGeneratorInterface + */ + public function getGenerator() + { + return new DefaultTimeGenerator( + $this->nodeProvider, + $this->timeConverter, + $this->timeProvider + ); + } +} diff --git a/vendor/ramsey/uuid/src/Generator/TimeGeneratorInterface.php b/vendor/ramsey/uuid/src/Generator/TimeGeneratorInterface.php new file mode 100644 index 0000000..27c7459 --- /dev/null +++ b/vendor/ramsey/uuid/src/Generator/TimeGeneratorInterface.php @@ -0,0 +1,43 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use Exception; +use InvalidArgumentException; +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; + +/** + * TimeGeneratorInterface provides functionality to generate strings of binary + * data for version 1 UUIDs based on a host ID, sequence number, and the current + * time + */ +interface TimeGeneratorInterface +{ + /** + * Generate a version 1 UUID from a host ID, sequence number, and the current time + * + * @param int|string $node A 48-bit number representing the hardware address + * This number may be represented as an integer or a hexadecimal string. + * @param int $clockSeq A 14-bit number used to help avoid duplicates that + * could arise when the clock is set backwards in time or if the node ID + * changes. + * @return string A binary string + * @throws UnsatisfiedDependencyException if called on a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + * @throws InvalidArgumentException + * @throws Exception if it was not possible to gather sufficient entropy + */ + public function generate($node = null, $clockSeq = null); +} diff --git a/vendor/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php b/vendor/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php new file mode 100644 index 0000000..83488ab --- /dev/null +++ b/vendor/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php @@ -0,0 +1,59 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider\Node; + +use Exception; +use Ramsey\Uuid\Provider\NodeProviderInterface; + +/** + * FallbackNodeProvider attempts to gain the system host ID from an array of + * providers, falling back to the next in line in the event a host ID can not be + * obtained + */ +class FallbackNodeProvider implements NodeProviderInterface +{ + /** + * @var NodeProviderInterface[] + */ + private $nodeProviders; + + /** + * Constructs a `FallbackNodeProvider` using an array of node providers + * + * @param NodeProviderInterface[] $providers Array of node providers + */ + public function __construct(array $providers) + { + $this->nodeProviders = $providers; + } + + /** + * Returns the system node ID by iterating over an array of node providers + * and returning the first non-empty value found + * + * @return string System node ID as a hexadecimal string + * @throws Exception + */ + public function getNode() + { + foreach ($this->nodeProviders as $provider) { + if ($node = $provider->getNode()) { + return $node; + } + } + + return null; + } +} diff --git a/vendor/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php b/vendor/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php new file mode 100644 index 0000000..79ec63c --- /dev/null +++ b/vendor/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php @@ -0,0 +1,57 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider\Node; + +use Exception; +use Ramsey\Uuid\Provider\NodeProviderInterface; + +/** + * RandomNodeProvider provides functionality to generate a random node ID, in + * the event that the node ID could not be obtained from the host system + * + * @link http://tools.ietf.org/html/rfc4122#section-4.5 + */ +class RandomNodeProvider implements NodeProviderInterface +{ + /** + * Returns the system node ID + * + * @return string System node ID as a hexadecimal string + * @throws Exception if it was not possible to gather sufficient entropy + */ + public function getNode() + { + $nodeBytes = random_bytes(6); + + // Split the node bytes for math on 32-bit systems. + $nodeMsb = substr($nodeBytes, 0, 3); + $nodeLsb = substr($nodeBytes, 3); + + // Set the multicast bit; see RFC 4122, section 4.5. + $nodeMsb = hex2bin( + str_pad( + dechex(hexdec(bin2hex($nodeMsb)) | 0x010000), + 6, + '0', + STR_PAD_LEFT + ) + ); + + // Recombine the node bytes. + $node = $nodeMsb . $nodeLsb; + + return str_pad(bin2hex($node), 12, '0', STR_PAD_LEFT); + } +} diff --git a/vendor/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php b/vendor/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php new file mode 100644 index 0000000..5701513 --- /dev/null +++ b/vendor/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php @@ -0,0 +1,128 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider\Node; + +use Ramsey\Uuid\Provider\NodeProviderInterface; + +/** + * SystemNodeProvider provides functionality to get the system node ID (MAC + * address) using external system calls + */ +class SystemNodeProvider implements NodeProviderInterface +{ + /** + * Returns the system node ID + * + * @return string|false System node ID as a hexadecimal string, or false if it is not found + */ + public function getNode() + { + static $node = null; + + if ($node !== null) { + return $node; + } + + $pattern = '/[^:]([0-9A-Fa-f]{2}([:-])[0-9A-Fa-f]{2}(\2[0-9A-Fa-f]{2}){4})[^:]/'; + $matches = []; + + // first try a linux specific way + $node = $this->getSysfs(); + + // Search the ifconfig output for all MAC addresses and return + // the first one found + if ($node === false) { + if (preg_match_all($pattern, $this->getIfconfig(), $matches, PREG_PATTERN_ORDER)) { + $node = $matches[1][0]; + } + } + if ($node !== false) { + $node = str_replace([':', '-'], '', $node); + } + return $node; + } + + /** + * Returns the network interface configuration for the system + * + * @codeCoverageIgnore + * @return string + */ + protected function getIfconfig() + { + if (strpos(strtolower(ini_get('disable_functions')), 'passthru') !== false) { + return ''; + } + + ob_start(); + switch (strtoupper(substr(constant('PHP_OS'), 0, 3))) { + case 'WIN': + passthru('ipconfig /all 2>&1'); + break; + case 'DAR': + passthru('ifconfig 2>&1'); + break; + case 'FRE': + passthru('netstat -i -f link 2>&1'); + break; + case 'LIN': + default: + passthru('netstat -ie 2>&1'); + break; + } + + return ob_get_clean(); + } + + /** + * Returns mac address from the first system interface via the sysfs interface + * + * @return string|bool + */ + protected function getSysfs() + { + $mac = false; + + if (strtoupper(constant('PHP_OS')) === 'LINUX') { + $addressPaths = glob('/sys/class/net/*/address', GLOB_NOSORT); + + if (empty($addressPaths)) { + return false; + } + + $macs = []; + array_walk($addressPaths, function ($addressPath) use (&$macs) { + if (is_readable($addressPath)) { + $macs[] = file_get_contents($addressPath); + } + }); + + $macs = array_map('trim', $macs); + + // remove invalid entries + $macs = array_filter($macs, function ($mac) { + return + // localhost adapter + $mac !== '00:00:00:00:00:00' && + // must match mac adress + preg_match('/^([0-9a-f]{2}:){5}[0-9a-f]{2}$/i', $mac); + }); + + $mac = reset($macs); + } + + return $mac; + } +} diff --git a/vendor/ramsey/uuid/src/Provider/NodeProviderInterface.php b/vendor/ramsey/uuid/src/Provider/NodeProviderInterface.php new file mode 100644 index 0000000..b6f721f --- /dev/null +++ b/vendor/ramsey/uuid/src/Provider/NodeProviderInterface.php @@ -0,0 +1,32 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider; + +use Exception; + +/** + * NodeProviderInterface provides functionality to get the node ID (or host ID + * in the form of the system's MAC address) from a specific type of node provider + */ +interface NodeProviderInterface +{ + /** + * Returns the system node ID + * + * @return string System node ID as a hexadecimal string + * @throws Exception if it was not possible to gather sufficient entropy + */ + public function getNode(); +} diff --git a/vendor/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php b/vendor/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php new file mode 100644 index 0000000..79a9d04 --- /dev/null +++ b/vendor/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php @@ -0,0 +1,77 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider\Time; + +use InvalidArgumentException; +use Ramsey\Uuid\Provider\TimeProviderInterface; + +/** + * FixedTimeProvider uses an previously-generated timestamp to provide the time + * + * This provider allows the use of a previously-generated timestamp, such as one + * stored in a database, when creating version 1 UUIDs. + */ +class FixedTimeProvider implements TimeProviderInterface +{ + /** + * @var int[] Array containing `sec` and `usec` components of a timestamp + */ + private $fixedTime; + + /** + * Constructs a `FixedTimeProvider` using the provided `$timestamp` + * + * @param int[] Array containing `sec` and `usec` components of a timestamp + * @throws InvalidArgumentException if the `$timestamp` does not contain `sec` or `usec` components + */ + public function __construct(array $timestamp) + { + if (!array_key_exists('sec', $timestamp) || !array_key_exists('usec', $timestamp)) { + throw new InvalidArgumentException('Array must contain sec and usec keys.'); + } + + $this->fixedTime = $timestamp; + } + + /** + * Sets the `usec` component of the timestamp + * + * @param int $value The `usec` value to set + */ + public function setUsec($value) + { + $this->fixedTime['usec'] = $value; + } + + /** + * Sets the `sec` component of the timestamp + * + * @param int $value The `sec` value to set + */ + public function setSec($value) + { + $this->fixedTime['sec'] = $value; + } + + /** + * Returns a timestamp array + * + * @return int[] Array containing `sec` and `usec` components of a timestamp + */ + public function currentTime() + { + return $this->fixedTime; + } +} diff --git a/vendor/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php b/vendor/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php new file mode 100644 index 0000000..6442985 --- /dev/null +++ b/vendor/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php @@ -0,0 +1,33 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider\Time; + +use Ramsey\Uuid\Provider\TimeProviderInterface; + +/** + * SystemTimeProvider uses built-in PHP functions to provide the time + */ +class SystemTimeProvider implements TimeProviderInterface +{ + /** + * Returns a timestamp array + * + * @return int[] Array containing `sec` and `usec` components of a timestamp + */ + public function currentTime() + { + return gettimeofday(); + } +} diff --git a/vendor/ramsey/uuid/src/Provider/TimeProviderInterface.php b/vendor/ramsey/uuid/src/Provider/TimeProviderInterface.php new file mode 100644 index 0000000..ef8099d --- /dev/null +++ b/vendor/ramsey/uuid/src/Provider/TimeProviderInterface.php @@ -0,0 +1,29 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Provider; + +/** + * TimeProviderInterface provides functionality to get the time from a specific + * type of time provider + */ +interface TimeProviderInterface +{ + /** + * Returns a timestamp array + * + * @return int[] Array guaranteed to contain `sec` and `usec` components of a timestamp + */ + public function currentTime(); +} diff --git a/vendor/ramsey/uuid/src/Uuid.php b/vendor/ramsey/uuid/src/Uuid.php new file mode 100644 index 0000000..38fbd5e --- /dev/null +++ b/vendor/ramsey/uuid/src/Uuid.php @@ -0,0 +1,751 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid; + +use DateTime; +use Exception; +use InvalidArgumentException; +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\Exception\InvalidUuidStringException; +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; +use Ramsey\Uuid\Exception\UnsupportedOperationException; + +/** + * Represents a universally unique identifier (UUID), according to RFC 4122. + * + * This class provides immutable UUID objects (the Uuid class) and the static + * methods `uuid1()`, `uuid3()`, `uuid4()`, and `uuid5()` for generating version + * 1, 3, 4, and 5 UUIDs as specified in RFC 4122. + * + * If all you want is a unique ID, you should probably call `uuid1()` or `uuid4()`. + * Note that `uuid1()` may compromise privacy since it creates a UUID containing + * the computer’s network address. `uuid4()` creates a random UUID. + * + * @link http://tools.ietf.org/html/rfc4122 + * @link http://en.wikipedia.org/wiki/Universally_unique_identifier + * @link http://docs.python.org/3/library/uuid.html + * @link http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html + */ +class Uuid implements UuidInterface +{ + /** + * When this namespace is specified, the name string is a fully-qualified domain name. + * @link http://tools.ietf.org/html/rfc4122#appendix-C + */ + const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; + + /** + * When this namespace is specified, the name string is a URL. + * @link http://tools.ietf.org/html/rfc4122#appendix-C + */ + const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; + + /** + * When this namespace is specified, the name string is an ISO OID. + * @link http://tools.ietf.org/html/rfc4122#appendix-C + */ + const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'; + + /** + * When this namespace is specified, the name string is an X.500 DN in DER or a text output format. + * @link http://tools.ietf.org/html/rfc4122#appendix-C + */ + const NAMESPACE_X500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; + + /** + * The nil UUID is special form of UUID that is specified to have all 128 bits set to zero. + * @link http://tools.ietf.org/html/rfc4122#section-4.1.7 + */ + const NIL = '00000000-0000-0000-0000-000000000000'; + + /** + * Reserved for NCS compatibility. + * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 + */ + const RESERVED_NCS = 0; + + /** + * Specifies the UUID layout given in RFC 4122. + * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 + */ + const RFC_4122 = 2; + + /** + * Reserved for Microsoft compatibility. + * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 + */ + const RESERVED_MICROSOFT = 6; + + /** + * Reserved for future definition. + * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 + */ + const RESERVED_FUTURE = 7; + + /** + * Regular expression pattern for matching a valid UUID of any variant. + */ + const VALID_PATTERN = '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'; + + /** + * Version 1 (time-based) UUID object constant identifier + */ + const UUID_TYPE_TIME = 1; + + /** + * Version 2 (identifier-based) UUID object constant identifier + */ + const UUID_TYPE_IDENTIFIER = 2; + + /** + * Version 3 (name-based and hashed with MD5) UUID object constant identifier + */ + const UUID_TYPE_HASH_MD5 = 3; + + /** + * Version 4 (random) UUID object constant identifier + */ + const UUID_TYPE_RANDOM = 4; + + /** + * Version 5 (name-based and hashed with SHA1) UUID object constant identifier + */ + const UUID_TYPE_HASH_SHA1 = 5; + + /** + * The factory to use when creating UUIDs. + * @var UuidFactoryInterface + */ + private static $factory = null; + + /** + * The codec to use when encoding or decoding UUID strings. + * @var CodecInterface + */ + protected $codec; + + /** + * The fields that make up this UUID. + * + * This is initialized to the nil value. + * + * @var array + * @see UuidInterface::getFieldsHex() + */ + protected $fields = [ + 'time_low' => '00000000', + 'time_mid' => '0000', + 'time_hi_and_version' => '0000', + 'clock_seq_hi_and_reserved' => '00', + 'clock_seq_low' => '00', + 'node' => '000000000000', + ]; + + /** + * The number converter to use for converting hex values to/from integers. + * @var NumberConverterInterface + */ + protected $converter; + + /** + * Creates a universally unique identifier (UUID) from an array of fields. + * + * Unless you're making advanced use of this library to generate identifiers + * that deviate from RFC 4122, you probably do not want to instantiate a + * UUID directly. Use the static methods, instead: + * + * ``` + * use Ramsey\Uuid\Uuid; + * + * $timeBasedUuid = Uuid::uuid1(); + * $namespaceMd5Uuid = Uuid::uuid3(Uuid::NAMESPACE_URL, 'http://php.net/'); + * $randomUuid = Uuid::uuid4(); + * $namespaceSha1Uuid = Uuid::uuid5(Uuid::NAMESPACE_URL, 'http://php.net/'); + * ``` + * + * @param array $fields An array of fields from which to construct a UUID; + * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. + * @param NumberConverterInterface $converter The number converter to use + * for converting hex values to/from integers. + * @param CodecInterface $codec The codec to use when encoding or decoding + * UUID strings. + */ + public function __construct( + array $fields, + NumberConverterInterface $converter, + CodecInterface $codec + ) { + $this->fields = $fields; + $this->codec = $codec; + $this->converter = $converter; + } + + /** + * Converts this UUID object to a string when the object is used in any + * string context. + * + * @return string + * @link http://www.php.net/manual/en/language.oop5.magic.php#object.tostring + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Converts this UUID object to a string when the object is serialized + * with `json_encode()` + * + * @return string + * @link http://php.net/manual/en/class.jsonserializable.php + */ + public function jsonSerialize() + { + return $this->toString(); + } + + /** + * Converts this UUID object to a string when the object is serialized + * with `serialize()` + * + * @return string + * @link http://php.net/manual/en/class.serializable.php + */ + public function serialize() + { + return $this->toString(); + } + + /** + * Re-constructs the object from its serialized form. + * + * @param string $serialized + * @link http://php.net/manual/en/class.serializable.php + * @throws InvalidUuidStringException + */ + public function unserialize($serialized) + { + $uuid = self::fromString($serialized); + $this->codec = $uuid->codec; + $this->converter = $uuid->converter; + $this->fields = $uuid->fields; + } + + public function compareTo(UuidInterface $other) + { + if ($this->getMostSignificantBitsHex() < $other->getMostSignificantBitsHex()) { + return -1; + } + + if ($this->getMostSignificantBitsHex() > $other->getMostSignificantBitsHex()) { + return 1; + } + + if ($this->getLeastSignificantBitsHex() < $other->getLeastSignificantBitsHex()) { + return -1; + } + + if ($this->getLeastSignificantBitsHex() > $other->getLeastSignificantBitsHex()) { + return 1; + } + + return 0; + } + + public function equals($other) + { + if (!$other instanceof UuidInterface) { + return false; + } + + return $this->compareTo($other) == 0; + } + + public function getBytes() + { + return $this->codec->encodeBinary($this); + } + + /** + * Returns the high field of the clock sequence multiplexed with the variant + * (bits 65-72 of the UUID). + * + * @return int Unsigned 8-bit integer value of clock_seq_hi_and_reserved + */ + public function getClockSeqHiAndReserved() + { + return hexdec($this->getClockSeqHiAndReservedHex()); + } + + public function getClockSeqHiAndReservedHex() + { + return $this->fields['clock_seq_hi_and_reserved']; + } + + /** + * Returns the low field of the clock sequence (bits 73-80 of the UUID). + * + * @return int Unsigned 8-bit integer value of clock_seq_low + */ + public function getClockSeqLow() + { + return hexdec($this->getClockSeqLowHex()); + } + + public function getClockSeqLowHex() + { + return $this->fields['clock_seq_low']; + } + + /** + * Returns the clock sequence value associated with this UUID. + * + * For UUID version 1, the clock sequence is used to help avoid + * duplicates that could arise when the clock is set backwards in time + * or if the node ID changes. + * + * For UUID version 3 or 5, the clock sequence is a 14-bit value + * constructed from a name as described in RFC 4122, Section 4.3. + * + * For UUID version 4, clock sequence is a randomly or pseudo-randomly + * generated 14-bit value as described in RFC 4122, Section 4.4. + * + * @return int Unsigned 14-bit integer value of clock sequence + * @link http://tools.ietf.org/html/rfc4122#section-4.1.5 + */ + public function getClockSequence() + { + return ($this->getClockSeqHiAndReserved() & 0x3f) << 8 | $this->getClockSeqLow(); + } + + public function getClockSequenceHex() + { + return sprintf('%04x', $this->getClockSequence()); + } + + public function getNumberConverter() + { + return $this->converter; + } + + /** + * @inheritdoc + */ + public function getDateTime() + { + if ($this->getVersion() != 1) { + throw new UnsupportedOperationException('Not a time-based UUID'); + } + + $unixTime = ($this->getTimestamp() - 0x01b21dd213814000) / 1e7; + $unixTime = number_format($unixTime, 0, '', ''); + + return new DateTime("@{$unixTime}"); + } + + /** + * Returns an array of the fields of this UUID, with keys named according + * to the RFC 4122 names for the fields. + * + * * **time_low**: The low field of the timestamp, an unsigned 32-bit integer + * * **time_mid**: The middle field of the timestamp, an unsigned 16-bit integer + * * **time_hi_and_version**: The high field of the timestamp multiplexed with + * the version number, an unsigned 16-bit integer + * * **clock_seq_hi_and_reserved**: The high field of the clock sequence + * multiplexed with the variant, an unsigned 8-bit integer + * * **clock_seq_low**: The low field of the clock sequence, an unsigned + * 8-bit integer + * * **node**: The spatially unique node identifier, an unsigned 48-bit + * integer + * + * @return array The UUID fields represented as integer values + * @link http://tools.ietf.org/html/rfc4122#section-4.1.2 + */ + public function getFields() + { + return [ + 'time_low' => $this->getTimeLow(), + 'time_mid' => $this->getTimeMid(), + 'time_hi_and_version' => $this->getTimeHiAndVersion(), + 'clock_seq_hi_and_reserved' => $this->getClockSeqHiAndReserved(), + 'clock_seq_low' => $this->getClockSeqLow(), + 'node' => $this->getNode(), + ]; + } + + public function getFieldsHex() + { + return $this->fields; + } + + public function getHex() + { + return str_replace('-', '', $this->toString()); + } + + /** + * @inheritdoc + */ + public function getInteger() + { + return $this->converter->fromHex($this->getHex()); + } + + /** + * Returns the least significant 64 bits of this UUID's 128 bit value. + * + * @return mixed Converted representation of the unsigned 64-bit integer value + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + */ + public function getLeastSignificantBits() + { + return $this->converter->fromHex($this->getLeastSignificantBitsHex()); + } + + public function getLeastSignificantBitsHex() + { + return sprintf( + '%02s%02s%012s', + $this->fields['clock_seq_hi_and_reserved'], + $this->fields['clock_seq_low'], + $this->fields['node'] + ); + } + + /** + * Returns the most significant 64 bits of this UUID's 128 bit value. + * + * @return mixed Converted representation of the unsigned 64-bit integer value + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + */ + public function getMostSignificantBits() + { + return $this->converter->fromHex($this->getMostSignificantBitsHex()); + } + + public function getMostSignificantBitsHex() + { + return sprintf( + '%08s%04s%04s', + $this->fields['time_low'], + $this->fields['time_mid'], + $this->fields['time_hi_and_version'] + ); + } + + /** + * Returns the node value associated with this UUID + * + * For UUID version 1, the node field consists of an IEEE 802 MAC + * address, usually the host address. For systems with multiple IEEE + * 802 addresses, any available one can be used. The lowest addressed + * octet (octet number 10) contains the global/local bit and the + * unicast/multicast bit, and is the first octet of the address + * transmitted on an 802.3 LAN. + * + * For systems with no IEEE address, a randomly or pseudo-randomly + * generated value may be used; see RFC 4122, Section 4.5. The + * multicast bit must be set in such addresses, in order that they + * will never conflict with addresses obtained from network cards. + * + * For UUID version 3 or 5, the node field is a 48-bit value constructed + * from a name as described in RFC 4122, Section 4.3. + * + * For UUID version 4, the node field is a randomly or pseudo-randomly + * generated 48-bit value as described in RFC 4122, Section 4.4. + * + * @return int Unsigned 48-bit integer value of node + * @link http://tools.ietf.org/html/rfc4122#section-4.1.6 + */ + public function getNode() + { + return hexdec($this->getNodeHex()); + } + + public function getNodeHex() + { + return $this->fields['node']; + } + + /** + * Returns the high field of the timestamp multiplexed with the version + * number (bits 49-64 of the UUID). + * + * @return int Unsigned 16-bit integer value of time_hi_and_version + */ + public function getTimeHiAndVersion() + { + return hexdec($this->getTimeHiAndVersionHex()); + } + + public function getTimeHiAndVersionHex() + { + return $this->fields['time_hi_and_version']; + } + + /** + * Returns the low field of the timestamp (the first 32 bits of the UUID). + * + * @return int Unsigned 32-bit integer value of time_low + */ + public function getTimeLow() + { + return hexdec($this->getTimeLowHex()); + } + + public function getTimeLowHex() + { + return $this->fields['time_low']; + } + + /** + * Returns the middle field of the timestamp (bits 33-48 of the UUID). + * + * @return int Unsigned 16-bit integer value of time_mid + */ + public function getTimeMid() + { + return hexdec($this->getTimeMidHex()); + } + + public function getTimeMidHex() + { + return $this->fields['time_mid']; + } + + /** + * Returns the timestamp value associated with this UUID. + * + * The 60 bit timestamp value is constructed from the time_low, + * time_mid, and time_hi fields of this UUID. The resulting + * timestamp is measured in 100-nanosecond units since midnight, + * October 15, 1582 UTC. + * + * The timestamp value is only meaningful in a time-based UUID, which + * has version type 1. If this UUID is not a time-based UUID then + * this method throws UnsupportedOperationException. + * + * @return int Unsigned 60-bit integer value of the timestamp + * @throws UnsupportedOperationException If this UUID is not a version 1 UUID + * @link http://tools.ietf.org/html/rfc4122#section-4.1.4 + */ + public function getTimestamp() + { + if ($this->getVersion() != 1) { + throw new UnsupportedOperationException('Not a time-based UUID'); + } + + return hexdec($this->getTimestampHex()); + } + + /** + * @inheritdoc + */ + public function getTimestampHex() + { + if ($this->getVersion() != 1) { + throw new UnsupportedOperationException('Not a time-based UUID'); + } + + return sprintf( + '%03x%04s%08s', + ($this->getTimeHiAndVersion() & 0x0fff), + $this->fields['time_mid'], + $this->fields['time_low'] + ); + } + + public function getUrn() + { + return 'urn:uuid:' . $this->toString(); + } + + public function getVariant() + { + $clockSeq = $this->getClockSeqHiAndReserved(); + + if (0 === ($clockSeq & 0x80)) { + return self::RESERVED_NCS; + } + + if (0 === ($clockSeq & 0x40)) { + return self::RFC_4122; + } + + if (0 === ($clockSeq & 0x20)) { + return self::RESERVED_MICROSOFT; + } + + return self::RESERVED_FUTURE; + } + + public function getVersion() + { + if ($this->getVariant() == self::RFC_4122) { + return (int) (($this->getTimeHiAndVersion() >> 12) & 0x0f); + } + + return null; + } + + public function toString() + { + return $this->codec->encode($this); + } + + /** + * Returns the currently set factory used to create UUIDs. + * + * @return UuidFactoryInterface + */ + public static function getFactory() + { + if (!self::$factory) { + self::$factory = new UuidFactory(); + } + + return self::$factory; + } + + /** + * Sets the factory used to create UUIDs. + * + * @param UuidFactoryInterface $factory + */ + public static function setFactory(UuidFactoryInterface $factory) + { + self::$factory = $factory; + } + + /** + * Creates a UUID from a byte string. + * + * @param string $bytes + * @return UuidInterface + * @throws InvalidUuidStringException + * @throws InvalidArgumentException + */ + public static function fromBytes($bytes) + { + return self::getFactory()->fromBytes($bytes); + } + + /** + * Creates a UUID from the string standard representation. + * + * @param string $name A string that specifies a UUID + * @return UuidInterface + * @throws InvalidUuidStringException + */ + public static function fromString($name) + { + return self::getFactory()->fromString($name); + } + + /** + * Creates a UUID from a 128-bit integer string. + * + * @param string $integer String representation of 128-bit integer + * @return UuidInterface + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws InvalidUuidStringException + */ + public static function fromInteger($integer) + { + return self::getFactory()->fromInteger($integer); + } + + /** + * Check if a string is a valid UUID. + * + * @param string $uuid The string UUID to test + * @return boolean + */ + public static function isValid($uuid) + { + $uuid = str_replace(['urn:', 'uuid:', '{', '}'], '', $uuid); + + if ($uuid == self::NIL) { + return true; + } + + if (!preg_match('/' . self::VALID_PATTERN . '/D', $uuid)) { + return false; + } + + return true; + } + + /** + * Generate a version 1 UUID from a host ID, sequence number, and the current time. + * + * @param int|string $node A 48-bit number representing the hardware address + * This number may be represented as an integer or a hexadecimal string. + * @param int $clockSeq A 14-bit number used to help avoid duplicates that + * could arise when the clock is set backwards in time or if the node ID + * changes. + * @return UuidInterface + * @throws UnsatisfiedDependencyException if called on a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + * @throws InvalidArgumentException + * @throws Exception if it was not possible to gather sufficient entropy + */ + public static function uuid1($node = null, $clockSeq = null) + { + return self::getFactory()->uuid1($node, $clockSeq); + } + + /** + * Generate a version 3 UUID based on the MD5 hash of a namespace identifier + * (which is a UUID) and a name (which is a string). + * + * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID + * @param string $name The name to create a UUID for + * @return UuidInterface + * @throws InvalidUuidStringException + */ + public static function uuid3($ns, $name) + { + return self::getFactory()->uuid3($ns, $name); + } + + /** + * Generate a version 4 (random) UUID. + * + * @return UuidInterface + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws InvalidArgumentException + * @throws Exception + */ + public static function uuid4() + { + return self::getFactory()->uuid4(); + } + + /** + * Generate a version 5 UUID based on the SHA-1 hash of a namespace + * identifier (which is a UUID) and a name (which is a string). + * + * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID + * @param string $name The name to create a UUID for + * @return UuidInterface + * @throws InvalidUuidStringException + */ + public static function uuid5($ns, $name) + { + return self::getFactory()->uuid5($ns, $name); + } +} diff --git a/vendor/ramsey/uuid/src/UuidFactory.php b/vendor/ramsey/uuid/src/UuidFactory.php new file mode 100644 index 0000000..5a57b09 --- /dev/null +++ b/vendor/ramsey/uuid/src/UuidFactory.php @@ -0,0 +1,315 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid; + +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\Exception\InvalidUuidStringException; +use Ramsey\Uuid\Provider\NodeProviderInterface; +use Ramsey\Uuid\Generator\RandomGeneratorInterface; +use Ramsey\Uuid\Generator\TimeGeneratorInterface; +use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\Builder\UuidBuilderInterface; + +class UuidFactory implements UuidFactoryInterface +{ + /** + * @var CodecInterface + */ + private $codec = null; + + /** + * @var NodeProviderInterface + */ + private $nodeProvider = null; + + /** + * @var NumberConverterInterface + */ + private $numberConverter = null; + + /** + * @var RandomGeneratorInterface + */ + private $randomGenerator = null; + + /** + * @var TimeGeneratorInterface + */ + private $timeGenerator = null; + + /** + * @var UuidBuilderInterface + */ + private $uuidBuilder = null; + + /** + * Constructs a `UuidFactory` for creating `Ramsey\Uuid\UuidInterface` instances + * + * @param FeatureSet $features A set of features for use when creating UUIDs + */ + public function __construct(FeatureSet $features = null) + { + $features = $features ?: new FeatureSet(); + + $this->codec = $features->getCodec(); + $this->nodeProvider = $features->getNodeProvider(); + $this->numberConverter = $features->getNumberConverter(); + $this->randomGenerator = $features->getRandomGenerator(); + $this->timeGenerator = $features->getTimeGenerator(); + $this->uuidBuilder = $features->getBuilder(); + } + + /** + * Returns the UUID coder-decoder used by this factory + * + * @return CodecInterface + */ + public function getCodec() + { + return $this->codec; + } + + /** + * Sets the UUID coder-decoder used by this factory + * + * @param CodecInterface $codec + */ + public function setCodec(CodecInterface $codec) + { + $this->codec = $codec; + } + + /** + * Returns the system node ID provider used by this factory + * + * @return NodeProviderInterface + */ + public function getNodeProvider() + { + return $this->nodeProvider; + } + + /** + * Returns the random UUID generator used by this factory + * + * @return RandomGeneratorInterface + */ + public function getRandomGenerator() + { + return $this->randomGenerator; + } + + /** + * Returns the time-based UUID generator used by this factory + * + * @return TimeGeneratorInterface + */ + public function getTimeGenerator() + { + return $this->timeGenerator; + } + + /** + * Sets the time-based UUID generator this factory will use to generate version 1 UUIDs + * + * @param TimeGeneratorInterface $generator + */ + public function setTimeGenerator(TimeGeneratorInterface $generator) + { + $this->timeGenerator = $generator; + } + + /** + * Returns the number converter used by this factory + * + * @return NumberConverterInterface + */ + public function getNumberConverter() + { + return $this->numberConverter; + } + + /** + * Sets the random UUID generator this factory will use to generate version 4 UUIDs + * + * @param RandomGeneratorInterface $generator + */ + public function setRandomGenerator(RandomGeneratorInterface $generator) + { + $this->randomGenerator = $generator; + } + + /** + * Sets the number converter this factory will use + * + * @param NumberConverterInterface $converter + */ + public function setNumberConverter(NumberConverterInterface $converter) + { + $this->numberConverter = $converter; + } + + /** + * Returns the UUID builder this factory uses when creating `Uuid` instances + * + * @return UuidBuilderInterface $builder + */ + public function getUuidBuilder() + { + return $this->uuidBuilder; + } + + /** + * Sets the UUID builder this factory will use when creating `Uuid` instances + * + * @param UuidBuilderInterface $builder + */ + public function setUuidBuilder(UuidBuilderInterface $builder) + { + $this->uuidBuilder = $builder; + } + + /** + * @inheritdoc + */ + public function fromBytes($bytes) + { + return $this->codec->decodeBytes($bytes); + } + + /** + * @inheritdoc + */ + public function fromString($uuid) + { + $uuid = strtolower($uuid); + return $this->codec->decode($uuid); + } + + /** + * @inheritdoc + */ + public function fromInteger($integer) + { + $hex = $this->numberConverter->toHex($integer); + $hex = str_pad($hex, 32, '0', STR_PAD_LEFT); + + return $this->fromString($hex); + } + + /** + * @inheritdoc + */ + public function uuid1($node = null, $clockSeq = null) + { + $bytes = $this->timeGenerator->generate($node, $clockSeq); + $hex = bin2hex($bytes); + + return $this->uuidFromHashedName($hex, 1); + } + + /** + * @inheritdoc + */ + public function uuid3($ns, $name) + { + return $this->uuidFromNsAndName($ns, $name, 3, 'md5'); + } + + /** + * @inheritdoc + */ + public function uuid4() + { + $bytes = $this->randomGenerator->generate(16); + + // When converting the bytes to hex, it turns into a 32-character + // hexadecimal string that looks a lot like an MD5 hash, so at this + // point, we can just pass it to uuidFromHashedName. + $hex = bin2hex($bytes); + + return $this->uuidFromHashedName($hex, 4); + } + + /** + * @inheritdoc + */ + public function uuid5($ns, $name) + { + return $this->uuidFromNsAndName($ns, $name, 5, 'sha1'); + } + + /** + * Returns a `Uuid` + * + * Uses the configured builder and codec and the provided array of hexadecimal + * value UUID fields to construct a `Uuid` object. + * + * @param array $fields An array of fields from which to construct a UUID; + * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. + * @return UuidInterface + */ + public function uuid(array $fields) + { + return $this->uuidBuilder->build($this->codec, $fields); + } + + /** + * Returns a version 3 or 5 namespaced `Uuid` + * + * @param string|UuidInterface $ns The UUID namespace to use + * @param string $name The string to hash together with the namespace + * @param int $version The version of UUID to create (3 or 5) + * @param string $hashFunction The hash function to use when hashing together + * the namespace and name + * @return UuidInterface + * @throws InvalidUuidStringException + */ + protected function uuidFromNsAndName($ns, $name, $version, $hashFunction) + { + if (!($ns instanceof UuidInterface)) { + $ns = $this->codec->decode($ns); + } + + $hash = call_user_func($hashFunction, ($ns->getBytes() . $name)); + + return $this->uuidFromHashedName($hash, $version); + } + + /** + * Returns a `Uuid` created from `$hash` with the version field set to `$version` + * and the variant field set for RFC 4122 + * + * @param string $hash The hash to use when creating the UUID + * @param int $version The UUID version to set for this hash (1, 3, 4, or 5) + * @return UuidInterface + */ + protected function uuidFromHashedName($hash, $version) + { + $timeHi = BinaryUtils::applyVersion(substr($hash, 12, 4), $version); + $clockSeqHi = BinaryUtils::applyVariant(hexdec(substr($hash, 16, 2))); + + $fields = [ + 'time_low' => substr($hash, 0, 8), + 'time_mid' => substr($hash, 8, 4), + 'time_hi_and_version' => str_pad(dechex($timeHi), 4, '0', STR_PAD_LEFT), + 'clock_seq_hi_and_reserved' => str_pad(dechex($clockSeqHi), 2, '0', STR_PAD_LEFT), + 'clock_seq_low' => substr($hash, 18, 2), + 'node' => substr($hash, 20, 12), + ]; + + return $this->uuid($fields); + } +} diff --git a/vendor/ramsey/uuid/src/UuidFactoryInterface.php b/vendor/ramsey/uuid/src/UuidFactoryInterface.php new file mode 100644 index 0000000..1c1651d --- /dev/null +++ b/vendor/ramsey/uuid/src/UuidFactoryInterface.php @@ -0,0 +1,108 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid; + +use Exception; +use InvalidArgumentException; +use Ramsey\Uuid\Exception\InvalidUuidStringException; +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; + +/** + * UuidFactoryInterface defines common functionality all `UuidFactory` instances + * must implement + */ +interface UuidFactoryInterface +{ + /** + * Generate a version 1 UUID from a host ID, sequence number, and the current time. + * + * @param int|string|null $node A 48-bit number representing the hardware address + * This number may be represented as an integer or a hexadecimal string. + * @param int|null $clockSeq A 14-bit number used to help avoid duplicates that + * could arise when the clock is set backwards in time or if the node ID + * changes. + * @return UuidInterface + * @throws UnsatisfiedDependencyException if called on a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + * @throws InvalidArgumentException + * @throws Exception if it was not possible to gather sufficient entropy + */ + public function uuid1($node = null, $clockSeq = null); + + /** + * Generate a version 3 UUID based on the MD5 hash of a namespace identifier + * (which is a UUID) and a name (which is a string). + * + * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID + * @param string $name The name to create a UUID for + * @return UuidInterface + * @throws InvalidUuidStringException + */ + public function uuid3($ns, $name); + + /** + * Generate a version 4 (random) UUID. + * + * @return UuidInterface + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws InvalidArgumentException + * @throws Exception + */ + public function uuid4(); + + /** + * Generate a version 5 UUID based on the SHA-1 hash of a namespace + * identifier (which is a UUID) and a name (which is a string). + * + * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID + * @param string $name The name to create a UUID for + * @return UuidInterface + * @throws InvalidUuidStringException + */ + public function uuid5($ns, $name); + + /** + * Creates a UUID from a byte string. + * + * @param string $bytes A 16-byte string representation of a UUID + * @return UuidInterface + * @throws InvalidUuidStringException + * @throws InvalidArgumentException if string has not 16 characters + */ + public function fromBytes($bytes); + + /** + * Creates a UUID from the string standard representation + * + * @param string $uuid A string representation of a UUID + * @return UuidInterface + * @throws InvalidUuidStringException + */ + public function fromString($uuid); + + /** + * Creates a `Uuid` from an integer representation + * + * The integer representation may be a real integer, a string integer, or + * an integer representation supported by a configured number converter. + * + * @param mixed $integer The integer to use when creating a `Uuid` from an + * integer; may be of any type understood by the configured number converter + * @return UuidInterface + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws InvalidUuidStringException + */ + public function fromInteger($integer); +} diff --git a/vendor/ramsey/uuid/src/UuidInterface.php b/vendor/ramsey/uuid/src/UuidInterface.php new file mode 100644 index 0000000..42a3ad7 --- /dev/null +++ b/vendor/ramsey/uuid/src/UuidInterface.php @@ -0,0 +1,274 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid; + +use DateTime; +use JsonSerializable; +use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; +use Ramsey\Uuid\Exception\UnsupportedOperationException; +use Serializable; + +/** + * UuidInterface defines common functionality for all universally unique + * identifiers (UUIDs) + */ +interface UuidInterface extends JsonSerializable, Serializable +{ + /** + * Compares this UUID to the specified UUID. + * + * The first of two UUIDs is greater than the second if the most + * significant field in which the UUIDs differ is greater for the first + * UUID. + * + * * Q. What's the value of being able to sort UUIDs? + * * A. Use them as keys in a B-Tree or similar mapping. + * + * @param UuidInterface $other UUID to which this UUID is compared + * @return int -1, 0 or 1 as this UUID is less than, equal to, or greater than `$uuid` + */ + public function compareTo(UuidInterface $other); + + /** + * Compares this object to the specified object. + * + * The result is true if and only if the argument is not null, is a UUID + * object, has the same variant, and contains the same value, bit for bit, + * as this UUID. + * + * @param object $other + * @return bool True if `$other` is equal to this UUID + */ + public function equals($other); + + /** + * Returns the UUID as a 16-byte string (containing the six integer fields + * in big-endian byte order). + * + * @return string + */ + public function getBytes(); + + /** + * Returns the number converter to use for converting hex values to/from integers. + * + * @return NumberConverterInterface + */ + public function getNumberConverter(); + + /** + * Returns the hexadecimal value of the UUID. + * + * @return string + */ + public function getHex(); + + /** + * Returns an array of the fields of this UUID, with keys named according + * to the RFC 4122 names for the fields. + * + * * **time_low**: The low field of the timestamp, an unsigned 32-bit integer + * * **time_mid**: The middle field of the timestamp, an unsigned 16-bit integer + * * **time_hi_and_version**: The high field of the timestamp multiplexed with + * the version number, an unsigned 16-bit integer + * * **clock_seq_hi_and_reserved**: The high field of the clock sequence + * multiplexed with the variant, an unsigned 8-bit integer + * * **clock_seq_low**: The low field of the clock sequence, an unsigned + * 8-bit integer + * * **node**: The spatially unique node identifier, an unsigned 48-bit + * integer + * + * @return array The UUID fields represented as hexadecimal values + */ + public function getFieldsHex(); + + /** + * Returns the high field of the clock sequence multiplexed with the variant + * (bits 65-72 of the UUID). + * + * @return string Hexadecimal value of clock_seq_hi_and_reserved + */ + public function getClockSeqHiAndReservedHex(); + + /** + * Returns the low field of the clock sequence (bits 73-80 of the UUID). + * + * @return string Hexadecimal value of clock_seq_low + */ + public function getClockSeqLowHex(); + + /** + * Returns the clock sequence value associated with this UUID. + * + * @return string Hexadecimal value of clock sequence + */ + public function getClockSequenceHex(); + + /** + * Returns a PHP `DateTime` object representing the timestamp associated + * with this UUID. + * + * The timestamp value is only meaningful in a time-based UUID, which + * has version type 1. If this UUID is not a time-based UUID then + * this method throws `UnsupportedOperationException`. + * + * @return DateTime A PHP DateTime representation of the date + * @throws UnsupportedOperationException If this UUID is not a version 1 UUID + * @throws UnsatisfiedDependencyException if called in a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + */ + public function getDateTime(); + + /** + * Returns the integer value of the UUID, converted to an appropriate number + * representation. + * + * @return mixed Converted representation of the unsigned 128-bit integer value + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + */ + public function getInteger(); + + /** + * Returns the least significant 64 bits of this UUID's 128 bit value. + * + * @return string Hexadecimal value of least significant bits + */ + public function getLeastSignificantBitsHex(); + + /** + * Returns the most significant 64 bits of this UUID's 128 bit value. + * + * @return string Hexadecimal value of most significant bits + */ + public function getMostSignificantBitsHex(); + + /** + * Returns the node value associated with this UUID + * + * For UUID version 1, the node field consists of an IEEE 802 MAC + * address, usually the host address. For systems with multiple IEEE + * 802 addresses, any available one can be used. The lowest addressed + * octet (octet number 10) contains the global/local bit and the + * unicast/multicast bit, and is the first octet of the address + * transmitted on an 802.3 LAN. + * + * For systems with no IEEE address, a randomly or pseudo-randomly + * generated value may be used; see RFC 4122, Section 4.5. The + * multicast bit must be set in such addresses, in order that they + * will never conflict with addresses obtained from network cards. + * + * For UUID version 3 or 5, the node field is a 48-bit value constructed + * from a name as described in RFC 4122, Section 4.3. + * + * For UUID version 4, the node field is a randomly or pseudo-randomly + * generated 48-bit value as described in RFC 4122, Section 4.4. + * + * @return string Hexadecimal value of node + * @link http://tools.ietf.org/html/rfc4122#section-4.1.6 + */ + public function getNodeHex(); + + /** + * Returns the high field of the timestamp multiplexed with the version + * number (bits 49-64 of the UUID). + * + * @return string Hexadecimal value of time_hi_and_version + */ + public function getTimeHiAndVersionHex(); + + /** + * Returns the low field of the timestamp (the first 32 bits of the UUID). + * + * @return string Hexadecimal value of time_low + */ + public function getTimeLowHex(); + + /** + * Returns the middle field of the timestamp (bits 33-48 of the UUID). + * + * @return string Hexadecimal value of time_mid + */ + public function getTimeMidHex(); + + /** + * Returns the timestamp value associated with this UUID. + * + * The 60 bit timestamp value is constructed from the time_low, + * time_mid, and time_hi fields of this UUID. The resulting + * timestamp is measured in 100-nanosecond units since midnight, + * October 15, 1582 UTC. + * + * The timestamp value is only meaningful in a time-based UUID, which + * has version type 1. If this UUID is not a time-based UUID then + * this method throws UnsupportedOperationException. + * + * @return string Hexadecimal value of the timestamp + * @throws UnsupportedOperationException If this UUID is not a version 1 UUID + * @link http://tools.ietf.org/html/rfc4122#section-4.1.4 + */ + public function getTimestampHex(); + + /** + * Returns the string representation of the UUID as a URN. + * + * @return string + * @link http://en.wikipedia.org/wiki/Uniform_Resource_Name + */ + public function getUrn(); + + /** + * Returns the variant number associated with this UUID. + * + * The variant number describes the layout of the UUID. The variant + * number has the following meaning: + * + * * 0 - Reserved for NCS backward compatibility + * * 2 - The RFC 4122 variant (used by this class) + * * 6 - Reserved, Microsoft Corporation backward compatibility + * * 7 - Reserved for future definition + * + * @return int + * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 + */ + public function getVariant(); + + /** + * Returns the version number associated with this UUID. + * + * The version number describes how this UUID was generated and has the + * following meaning: + * + * * 1 - Time-based UUID + * * 2 - DCE security UUID + * * 3 - Name-based UUID hashed with MD5 + * * 4 - Randomly generated UUID + * * 5 - Name-based UUID hashed with SHA-1 + * + * Returns null if this UUID is not an RFC 4122 variant, since version + * is only meaningful for this variant. + * + * @return int|null + * @link http://tools.ietf.org/html/rfc4122#section-4.1.3 + */ + public function getVersion(); + + /** + * Converts this UUID into a string representation. + * + * @return string + */ + public function toString(); +} diff --git a/vendor/ramsey/uuid/src/functions.php b/vendor/ramsey/uuid/src/functions.php new file mode 100644 index 0000000..b5db341 --- /dev/null +++ b/vendor/ramsey/uuid/src/functions.php @@ -0,0 +1,78 @@ + + * @license http://opensource.org/licenses/MIT MIT + */ + +namespace Ramsey\Uuid; + +use Exception; +use InvalidArgumentException; +use Ramsey\Uuid\Exception\InvalidUuidStringException; +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; + +/** + * Generate a version 1 UUID from a host ID, sequence number, and the current time. + * + * @param int|string|null $node A 48-bit number representing the hardware address + * This number may be represented as an integer or a hexadecimal string. + * @param int|null $clockSeq A 14-bit number used to help avoid duplicates that + * could arise when the clock is set backwards in time or if the node ID + * changes. + * @return string + * @throws UnsatisfiedDependencyException if called on a 32-bit system and + * `Moontoast\Math\BigNumber` is not present + * @throws InvalidArgumentException + * @throws Exception if it was not possible to gather sufficient entropy + */ +function v1($node = null, $clockSeq = null) +{ + return Uuid::uuid1($node, $clockSeq)->toString(); +} + +/** + * Generate a version 3 UUID based on the MD5 hash of a namespace identifier + * (which is a UUID) and a name (which is a string). + * + * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID + * @param string $name The name to create a UUID for + * @return string + * @throws InvalidUuidStringException + */ +function v3($ns, $name) +{ + return Uuid::uuid3($ns, $name)->toString(); +} + +/** + * Generate a version 4 (random) UUID. + * + * @return string + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws InvalidArgumentException + * @throws Exception + */ +function v4() +{ + return Uuid::uuid4()->toString(); +} + +/** + * Generate a version 5 UUID based on the SHA-1 hash of a namespace + * identifier (which is a UUID) and a name (which is a string). + * + * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID + * @param string $name The name to create a UUID for + * @return string + * @throws InvalidUuidStringException + */ +function v5($ns, $name) +{ + return Uuid::uuid5($ns, $name)->toString(); +} diff --git a/vendor/services.php b/vendor/services.php new file mode 100644 index 0000000..540950c --- /dev/null +++ b/vendor/services.php @@ -0,0 +1,7 @@ + 'think\\app\\Service', + 1 => 'think\\swoole\\Service', +); \ No newline at end of file diff --git a/vendor/swoole/ide-helper/.gitignore b/vendor/swoole/ide-helper/.gitignore new file mode 100644 index 0000000..347a927 --- /dev/null +++ b/vendor/swoole/ide-helper/.gitignore @@ -0,0 +1,4 @@ +/.idea/ +/composer.lock +/composer.phar +/vendor/ diff --git a/vendor/swoole/ide-helper/.travis.yml b/vendor/swoole/ide-helper/.travis.yml new file mode 100644 index 0000000..df2c121 --- /dev/null +++ b/vendor/swoole/ide-helper/.travis.yml @@ -0,0 +1,10 @@ +language: php + +php: + - 7.3 + +before_install: + - composer update -n + +script: + - ./vendor/bin/phpcs -v --standard=PSR12 bin src diff --git a/vendor/swoole/ide-helper/LICENSE b/vendor/swoole/ide-helper/LICENSE new file mode 100644 index 0000000..ad00f19 --- /dev/null +++ b/vendor/swoole/ide-helper/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright Tianfeng.Han [mikan.tenny@gmail.com] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/swoole/ide-helper/README.md b/vendor/swoole/ide-helper/README.md new file mode 100644 index 0000000..36975ce --- /dev/null +++ b/vendor/swoole/ide-helper/README.md @@ -0,0 +1,47 @@ +# Swoole IDE Helper + +[![Build Status](https://travis-ci.org/swoole/ide-helper.svg?branch=master)](https://travis-ci.org/swoole/ide-helper) +[![Latest Stable Version](https://poser.pugx.org/swoole/ide-helper/v/stable.svg)](https://packagist.org/packages/swoole/ide-helper) +[![License](https://poser.pugx.org/swoole/ide-helper/license)](LICENSE) + +This package contains IDE help files for [Swoole](https://github.com/swoole/swoole-src). You may use it in your IDE to provide accurate autocompletion. + +## Install + +You may add this package to your project using [Composer](https://getcomposer.org): + +```bash +composer require swoole/ide-helper:@dev +# or you may install a specific version, like: +composer require swoole/ide-helper:~4.4.7 +``` + +It's better to install this package on only development systems by adding the `--dev` flag to your Composer commands: + +```bash +composer require --dev swoole/ide-helper:@dev +# or you may install a specific version, like: +composer require --dev swoole/ide-helper:~4.4.7 +``` + +## Alternatives + +The stubs are created by reverse-engineering the Swoole extensions directly; thus there is no documentation included, +and type hinting is missing in many places. The Swoole team has tried its best to keep the stubs up to date, and we do +want to add inline documentation and type hinting in the future; however, due to limited resources we don't know when it +will be ready. + +Here are some alternatives you can consider: + +* [eaglewu/swoole-ide-helper](https://github.com/wudi/swoole-ide-helper) +* [swoft/swoole-ide-helper](https://github.com/swoft-cloud/swoole-ide-helper) + +## Generate IDE Help Files + +Have Docker running first, then use script _./bin/generator.sh_ to generate IDE help files and put them under folder +`output/` with commands like following: + +```bash +./bin/generator.sh # To generate stubs with latest code from the master branch of Swoole. +./bin/generator.sh 4.4.7 # To generate stubs for a specific version of Swoole. +``` diff --git a/vendor/swoole/ide-helper/bin/generator.php b/vendor/swoole/ide-helper/bin/generator.php new file mode 100644 index 0000000..c2e4421 --- /dev/null +++ b/vendor/swoole/ide-helper/bin/generator.php @@ -0,0 +1,31 @@ +#!/usr/bin/env php +export(); + echo "IDE help files for {$generator->getExtension()} {$generator->getVersion()} are generated successfully.\n"; +} diff --git a/vendor/swoole/ide-helper/bin/generator.sh b/vendor/swoole/ide-helper/bin/generator.sh new file mode 100644 index 0000000..8b5635d --- /dev/null +++ b/vendor/swoole/ide-helper/bin/generator.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# To generate IDE help files of Swoole. +# +# How to use this script: +# ./bin/generator.sh # To generate stubs with latest code from the master branch of Swoole. +# ./bin/generator.sh 4.4.7 # To generate stubs for a specific version of Swoole. +# + +set -e + +pushd "`dirname "$0"`" > /dev/null +ROOT_PATH="`pwd -P`/.." +popd > /dev/null # Switch back to current directory. + +cd "${ROOT_PATH}" # Switch to root directory of project "ide-helper". + +if [ -z "${1}" ] ; then + echo INFO: Generating stubs with latest code from the master branch of Swoole. + image_tag=latest +else + if [[ "${1}" =~ ^[1-9][0-9]*\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[A-Za-z0-9_]+)?$ ]] ; then + echo INFO: Generating stubs for Swoole ${1}. + image_tag=${1}-php7.3 + else + echo "Error: '${1}' is not a valid Swoole version." + exit 1 + fi +fi +image_tag=${image_tag}-dev + +rm -rf ./output +docker run --rm \ + -v "`pwd`":/var/www \ + -e SWOOLE_EXT_ASYNC=enabled \ + -e SWOOLE_EXT_ORM=enabled \ + -e SWOOLE_EXT_POSTGRESQL=enabled \ + -e SWOOLE_EXT_SERIALIZE=enabled \ + -e SWOOLE_EXT_ZOOKEEPER=enabled \ + -t phpswoole/swoole:${image_tag} \ + bash -c "composer install && SWOOLE_SRC_DIR=/usr/src/swoole ./bin/generator.php" +git add ./output diff --git a/vendor/swoole/ide-helper/composer.json b/vendor/swoole/ide-helper/composer.json new file mode 100644 index 0000000..8701720 --- /dev/null +++ b/vendor/swoole/ide-helper/composer.json @@ -0,0 +1,22 @@ +{ + "name": "swoole/ide-helper", + "type": "library", + "description": "IDE help files for Swoole.", + "license": "Apache-2.0", + "authors": [ + { + "name": "Team Swoole", + "email": "team@swoole.com" + } + ], + "require-dev": { + "squizlabs/php_codesniffer": "~3.4.0", + "symfony/filesystem": "~4.3.0", + "zendframework/zend-code": "~3.3.0" + }, + "autoload-dev": { + "psr-4": { + "Swoole\\IDEHelper\\": "src/" + } + } +} diff --git a/vendor/swoole/ide-helper/config/chinese/server/method/send.php b/vendor/swoole/ide-helper/config/chinese/server/method/send.php new file mode 100644 index 0000000..f6061c1 --- /dev/null +++ b/vendor/swoole/ide-helper/config/chinese/server/method/send.php @@ -0,0 +1,27 @@ + ' + * 向客户端发送数据 + * + * * $data,发送的数据。TCP协议最大不得超过2M,UDP协议不得超过64K + * * 发送成功会返回true,如果连接已被关闭或发送失败会返回false + * + * TCP服务器 + * ------------------------------------------------------------------------- + * * send操作具有原子性,多个进程同时调用send向同一个连接发送数据,不会发生数据混杂 + * * 如果要发送超过2M的数据,可以将数据写入临时文件,然后通过sendfile接口进行发送 + * + * swoole-1.6以上版本不需要$from_id + * + * UDP服务器 + * ------------------------------------------------------------------------ + * * send操作会直接在worker进程内发送数据包,不会再经过主进程转发 + * * 使用fd保存客户端IP,from_id保存from_fd和port + * * 如果在onReceive后立即向客户端发送数据,可以不传$reactor_id + * * 如果向其他UDP客户端发送数据,必须要传入$reactor_id + * * 在外网服务中发送超过64K的数据会分成多个传输单元进行发送,如果其中一个单元丢包,会导致整个包被丢弃。所以外网服务,建议发送1.5K以下的数据包 + *', + //返回值 + 'return' => 'bool' +); \ No newline at end of file diff --git a/vendor/swoole/ide-helper/output/swoole/aliases.php b/vendor/swoole/ide-helper/output/swoole/aliases.php new file mode 100644 index 0000000..f43ddb4 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole/aliases.php @@ -0,0 +1,50 @@ +add($fn, ...$args); + return $s->start(); + } + +} + +namespace Co { + + if (SWOOLE_USE_SHORTNAME) { + function run(callable $fn, ...$args) + { + return \Swoole\Coroutine\Run($fn, ...$args); + } + } + +} diff --git a/vendor/swoole/ide-helper/output/swoole_lib/constants.php b/vendor/swoole/ide-helper/output/swoole_lib/constants.php new file mode 100644 index 0000000..5a20c02 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_lib/constants.php @@ -0,0 +1,6 @@ +array = $array; + } + + /** + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->array); + } + + /** + * @return int + */ + public function count(): int + { + return count($this->array); + } + + /** + * @return mixed + */ + public function current() + { + return current($this->array); + } + + /** + * @return mixed + */ + public function key() + { + return key($this->array); + } + + /** + * @return bool + */ + public function valid(): bool + { + return array_key_exists($this->key(), $this->array); + } + + /** + * @return mixed + */ + public function rewind() + { + return reset($this->array); + } + + /** + * @return mixed + */ + public function next() + { + return next($this->array); + } + + /** + * @param $key + * @return ArrayObject|StringObject|mixed + */ + public function get($key) + { + return static::detectType($this->array[$key]); + } + + /** + * @param $key + * @param $value + * @return $this + */ + public function set($key, $value): self + { + $this->array[$key] = $value; + return $this; + } + + /** + * @param $key + * @return $this + */ + public function delete($key): self + { + unset($this->array[$key]); + return $this; + } + + /** + * @param $value + * @param bool $strict + * @param bool $loop + * @return $this + */ + public function remove($value, bool $strict = true, bool $loop = false): self + { + do { + $key = $this->search($value, $strict); + if ($key) { + unset($this->array[$key]); + } else { + break; + } + } while ($loop); + return $this; + } + + /** + * @return $this + */ + public function clear(): self + { + $this->array = []; + return $this; + } + + /** + * @param mixed $key + * @return mixed|null + */ + public function offsetGet($key) + { + if (!array_key_exists($key, $this->array)) { + return null; + } + return $this->array[$key]; + } + + /** + * @param mixed $key + * @param mixed $value + */ + public function offsetSet($key, $value) + { + $this->array[$key] = $value; + } + + /** + * @param mixed $key + */ + public function offsetUnset($key) + { + unset($this->array[$key]); + } + + /** + * @param mixed $key + * @return bool + */ + public function offsetExists($key) + { + return isset($this->array[$key]); + } + + /** + * @param $key + * @return bool + */ + public function exists($key): bool + { + return array_key_exists($key, $this->array); + } + + /** + * @param $value + * @param bool $strict + * @return bool + */ + public function contains($value, bool $strict = true): bool + { + return in_array($value, $this->array, $strict); + } + + /** + * @param $value + * @param bool $strict + * @return mixed + */ + public function indexOf($value, bool $strict = true) + { + return $this->search($value, $strict); + } + + /** + * @param $value + * @param bool $strict + * @return mixed + */ + public function lastIndexOf($value, bool $strict = true) + { + $array = $this->array; + for (end($array); ($currentKey = key($array)) !== null; prev($array)) { + $currentValue = current($array); + if ($currentValue == $value) { + if ($strict && $currentValue !== $value) { + continue; + } + break; + } + } + return $currentKey; + } + + /** + * @param $needle + * @param $strict + * @return mixed + */ + public function search($needle, $strict = true) + { + return array_search($needle, $this->array, $strict); + } + + /** + * @param string $glue + * @return StringObject + */ + public function join(string $glue = ''): StringObject + { + return static::detectStringType(implode($glue, $this->array)); + } + + /** + * @return StringObject + */ + public function serialize(): StringObject + { + return static::detectStringType(serialize($this->array)); + } + + /** + * @param string $string + * @return $this + */ + public function unserialize($string): self + { + $this->array = (array)unserialize($string); + return $this; + } + + /** + * @return float|int + */ + public function sum() + { + return array_sum($this->array); + } + + /** + * @return float|int + */ + public function product() + { + return array_product($this->array); + } + + /** + * @param $value + * @return int + */ + public function push($value) + { + return array_push($this->array, $value); + } + + /** + * @param $value + * @return int + */ + public function pushBack($value) + { + return array_unshift($this->array, $value); + } + + /** + * @param int $offset + * @param $value + * @return $this + */ + public function insert(int $offset, $value): self + { + if (is_array($value) || is_object($value) || is_null($value)) { + $value = [$value]; + } + array_splice($this->array, $offset, 0, $value); + return $this; + } + + /** + * @return mixed + */ + public function pop() + { + return array_pop($this->array); + } + + /** + * @return mixed + */ + public function popFront() + { + return array_shift($this->array); + } + + /** + * @param $offset + * @param int $length + * @param bool $preserve_keys + * @return static + */ + public function slice($offset, int $length = null, bool $preserve_keys = false): self + { + return new static(array_slice($this->array, ...func_get_args())); + } + + /** + * @return ArrayObject|StringObject|mixed + */ + public function randomGet() + { + return static::detectType($this->array[array_rand($this->array, 1)]); + } + + /** + * @param $fn callable + * @return $this + */ + public function each(callable $fn): self + { + if (array_walk($this->array, $fn) === false) { + throw new RuntimeException('array_walk() failed'); + } + return $this; + } + + /** + * @param $fn callable + * @return static + */ + public function map(callable $fn): self + { + return new static(array_map($fn, $this->array)); + } + + /** + * @param $fn callable + * @return mixed + */ + public function reduce(callable $fn) + { + return array_reduce($this->array, $fn); + } + + /** + * @param int $search_value + * @param bool $strict + * @return static + */ + public function keys(int $search_value = null, $strict = false): self + { + return new static(array_keys($this->array, $search_value, $strict)); + } + + /** + * @return static + */ + public function values(): self + { + return new static(array_values($this->array)); + } + + /** + * @param $column_key + * @param mixed ...$index + * @return static + */ + public function column($column_key, ...$index): self + { + return new static(array_column($this->array, $column_key, ...$index)); + } + + /** + * @param int $sort_flags + * @return static + */ + public function unique(int $sort_flags = SORT_STRING): self + { + return new static(array_unique($this->array, $sort_flags)); + } + + /** + * @param bool $preserve_keys + * @return static + */ + public function reverse(bool $preserve_keys = false): self + { + return new static(array_reverse($this->array, $preserve_keys)); + } + + /** + * @param int $size + * @param bool $preserve_keys + * @return static + */ + public function chunk(int $size, bool $preserve_keys = false): self + { + return new static(array_chunk($this->array, $size, $preserve_keys)); + } + + /** + * Swap keys and values in an array + * @return static + */ + public function flip(): self + { + return new static(array_flip($this->array)); + } + + /** + * @param $fn callable + * @param int $flag + * @return static + */ + public function filter(callable $fn, int $flag = 0): self + { + return new static(array_filter($this->array, $fn, $flag)); + } + + /** + * | Function name | Sorts by | Maintains key association | Order of sort | Related functions | + * | :---------------- | :------- | :-------------------------- | :-------------------------- | :---------------- | + * | array_multisort() | value | associative yes, numeric no | first array or sort options | array_walk() | + * | asort() | value | yes | low to high | arsort() | + * | arsort() | value | yes | high to low | asort() | + * | krsort() | key | yes | high to low | ksort() | + * | ksort() | key | yes | low to high | asort() | + * | natcasesort() | value | yes | natural, case insensitive | natsort() | + * | natsort() | value | yes | natural | natcasesort() | + * | rsort() | value | no | high to low | sort() | + * | shuffle() | value | no | random | array_rand() | + * | sort() | value | no | low to high | rsort() | + * | uasort() | value | yes | user defined | uksort() | + * | uksort() | key | yes | user defined | uasort() | + * | usort() | value | no | user defined | uasort() | + */ + + /** + * @param int $sort_order + * @param int $sort_flags + * @return $this + */ + public function multiSort(int $sort_order = SORT_ASC, int $sort_flags = SORT_REGULAR): self + { + if (array_multisort($this->array, $sort_order, $sort_flags) !== true) { + throw new RuntimeException('array_multisort() failed'); + } + return $this; + } + + /** + * @param int $sort_flags + * @return $this + */ + public function asort(int $sort_flags = SORT_REGULAR): self + { + if (asort($this->array, $sort_flags) !== true) { + throw new RuntimeException('asort() failed'); + } + return $this; + } + + /** + * @param int $sort_flags + * @return $this + */ + public function arsort(int $sort_flags = SORT_REGULAR): self + { + if (arsort($this->array, $sort_flags) !== true) { + throw new RuntimeException('arsort() failed'); + } + return $this; + } + + /** + * @param int $sort_flags + * @return $this + */ + public function krsort(int $sort_flags = SORT_REGULAR): self + { + if (krsort($this->array, $sort_flags) !== true) { + throw new RuntimeException('krsort() failed'); + } + return $this; + } + + /** + * @param int $sort_flags + * @return $this + */ + public function ksort(int $sort_flags = SORT_REGULAR): self + { + if (ksort($this->array, $sort_flags) !== true) { + throw new RuntimeException('ksort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function natcasesort(): self + { + if (natcasesort($this->array) !== true) { + throw new RuntimeException('natcasesort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function natsort(): self + { + if (natsort($this->array) !== true) { + throw new RuntimeException('natsort() failed'); + } + return $this; + } + + /** + * @param int $sort_flags + * @return $this + */ + public function rsort(int $sort_flags = SORT_REGULAR): self + { + if (rsort($this->array, $sort_flags) !== true) { + throw new RuntimeException('rsort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function shuffle(): self + { + if (shuffle($this->array) !== true) { + throw new RuntimeException('shuffle() failed'); + } + return $this; + } + + /** + * @param int $sort_flags + * @return $this + */ + public function sort(int $sort_flags = SORT_REGULAR): self + { + if (sort($this->array, $sort_flags) !== true) { + throw new RuntimeException('sort() failed'); + } + return $this; + } + + /** + * @param callable $value_compare_func + * @return $this + */ + public function uasort(callable $value_compare_func): self + { + if (uasort($this->array, $value_compare_func) !== true) { + throw new RuntimeException('uasort() failed'); + } + return $this; + } + + /** + * @param callable $value_compare_func + * @return $this + */ + public function uksort(callable $value_compare_func): self + { + if (uksort($this->array, $value_compare_func) !== true) { + throw new RuntimeException('uksort() failed'); + } + return $this; + } + + /** + * @param callable $value_compare_func + * @return $this + */ + public function usort(callable $value_compare_func): self + { + if (usort($this->array, $value_compare_func) !== true) { + throw new RuntimeException('usort() failed'); + } + return $this; + } + + /** + * @return array + */ + public function __toArray(): array + { + return $this->array; + } + + /** + * @param $value + * @return ArrayObject|StringObject|mixed + */ + protected static function detectType($value) + { + if (is_string($value)) { + return static::detectStringType($value); + } elseif (is_array($value)) { + return static::detectArrayType($value); + } else { + return $value; + } + } + + /** + * @param string $value + * @return StringObject + */ + protected static function detectStringType(string $value): StringObject + { + return new StringObject($value); + } + + /** + * @param array $value + * @return static + */ + protected static function detectArrayType(array $value): self + { + return new static($value); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_lib/core/Constant.php b/vendor/swoole/ide-helper/output/swoole_lib/core/Constant.php new file mode 100644 index 0000000..80e2ab4 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_lib/core/Constant.php @@ -0,0 +1,186 @@ +object_pool = new Channel($pool_size); + $this->busy_pool = new Channel($concurrency); + $this->type = $type; + } + + public function get() + { + $context = Coroutine::getContext(); + if (!$context) { + throw new BadMethodCallException('ObjectPool misuse: get must be used in coroutine'); + } + $type = $this->type; + Coroutine::defer(function () { + $this->free(); + }); + if (isset($context[$type])) { + return $context[$type]; + } + if (!$this->object_pool->isEmpty()) { + $object = $this->object_pool->pop(); + $context["new"] = false; + } else { + /* create concurrency control */ + $this->busy_pool->push(true); + $object = $this->create(); + if (empty($object)) { + throw new RuntimeException('ObjectPool misuse: create object failed'); + } + $context["new"] = true; + } + + $context[$type] = $object; + return $object; + } + + public function free() + { + $context = Coroutine::getContext(); + if (!$context) { + throw new BadMethodCallException('ObjectPool misuse: free must be used in coroutine'); + } + $type = $this->type; + $object = $context[$type]; + $this->object_pool->push($object); + if ($context["new"]) { + $this->busy_pool->pop(); + } + } + + public abstract function create(); +} diff --git a/vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/Server.php b/vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/Server.php new file mode 100644 index 0000000..9b172d5 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/Server.php @@ -0,0 +1,125 @@ +contains('::')) { + $this->type = AF_INET6; + } else { + if ($_host->startsWith('unix:/')) { + $host = $_host->substr(5)->__toString(); + $this->type = AF_UNIX; + } else { + $this->type = AF_INET; + } + } + $this->host = $host; + + $socket = new Socket($this->type, SOCK_STREAM, 0); + if ($reuse_port and defined('SO_REUSEPORT')) { + $socket->setOption(SOL_SOCKET, SO_REUSEPORT, true); + } + if (!$socket->bind($this->host, $port)) { + throw new Exception("bind({$this->host}:{$port}) failed", $socket->errCode); + } + if (!$socket->listen()) { + throw new Exception("listen() failed", $socket->errCode); + } + $this->port = $socket->getsockname()['port'] ?? 0; + $this->fd = $socket->fd; + $this->socket = $socket; + $this->setting['open_ssl'] = $ssl; + } + + public function set(array $setting): void + { + $this->setting = array_merge($this->setting, $setting); + } + + public function handle(callable $fn): void + { + $this->fn = $fn; + } + + public function shutdown(): bool + { + $this->running = false; + return $this->socket->cancel(); + } + + public function start(): bool + { + $this->running = true; + if ($this->fn == null) { + $this->errCode = SOCKET_EINVAL; + return false; + } + $socket = $this->socket; + if (!$socket->setProtocol($this->setting)) { + $this->errCode = SOCKET_EINVAL; + return false; + } + + while ($this->running) { + /** @var $conn Socket */ + $conn = $socket->accept(); + if ($conn) { + $conn->setProtocol($this->setting); + if (Coroutine::create($this->fn, new Connection($conn)) < 0) { + goto _wait; + } + } else { + if ($socket->errCode == SOCKET_EMFILE or $socket->errCode == SOCKET_ENFILE) { + _wait: + Coroutine::sleep(1); + continue; + } elseif ($socket->errCode == SOCKET_ETIMEDOUT) { + continue; + } elseif ($socket->errCode == SOCKET_ECANCELED) { + break; + } else { + trigger_error("accept failed, Error: {$socket->errMsg}[{$socket->errCode}]", E_USER_WARNING); + break; + } + } + } + + return true; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/Server/Connection.php b/vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/Server/Connection.php new file mode 100644 index 0000000..3481732 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/Server/Connection.php @@ -0,0 +1,30 @@ +socket = $conn; + } + + public function recv($timeout = 0) + { + return $this->socket->recvPacket($timeout); + } + + public function send($data) + { + return $this->socket->sendAll($data); + } + + public function close() + { + return $this->socket->close(); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/WaitGroup.php b/vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/WaitGroup.php new file mode 100644 index 0000000..e01f0ea --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_lib/core/Coroutine/WaitGroup.php @@ -0,0 +1,53 @@ +chan = new Channel(1); + } + + public function add(int $delta = 1): void + { + if ($this->waiting) { + throw new BadMethodCallException('WaitGroup misuse: add called concurrently with wait'); + } + $count = $this->count + $delta; + if ($count < 0) { + throw new InvalidArgumentException('negative WaitGroup counter'); + } + $this->count = $count; + } + + public function done(): void + { + $count = $this->count - 1; + if ($count < 0) { + throw new BadMethodCallException('negative WaitGroup counter'); + } + $this->count = $count; + if ($count === 0 && $this->waiting) { + $this->chan->push(true); + } + } + + public function wait(float $timeout = -1): bool + { + if ($this->count > 0) { + $this->waiting = true; + $done = $this->chan->pop($timeout); + $this->waiting = false; + return $done; + } + return true; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_lib/core/Curl/Exception.php b/vendor/swoole/ide-helper/output/swoole_lib/core/Curl/Exception.php new file mode 100644 index 0000000..c73684d --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_lib/core/Curl/Exception.php @@ -0,0 +1,10 @@ + 'No URL set!', + ]; + + /** + * @var Client + */ + private $client; + private $info = [ + 'url' => '', + 'content_type' => '', + 'http_code' => 0, + 'header_size' => 0, + 'request_size' => 0, + 'filetime' => -1, + 'ssl_verify_result' => 0, + 'redirect_count' => 0, + 'total_time' => 5.3E-5, + 'namelookup_time' => 0.0, + 'connect_time' => 0.0, + 'pretransfer_time' => 0.0, + 'size_upload' => 0.0, + 'size_download' => 0.0, + 'speed_download' => 0.0, + 'speed_upload' => 0.0, + 'download_content_length' => -1.0, + 'upload_content_length' => -1.0, + 'starttransfer_time' => 0.0, + 'redirect_time' => 0.0, + 'redirect_url' => '', + 'primary_ip' => '', + 'certinfo' => [], + 'primary_port' => 0, + 'local_ip' => '', + 'local_port' => 0, + 'http_version' => 0, + 'protocol' => 0, + 'ssl_verifyresult' => 0, + 'scheme' => '', + ]; + + private $withHeaderOut = false; + private $withFileTime = false; + private $urlInfo; + private $postData; + private $outputStream; + private $proxy; + private $clientOptions = []; + private $followLocation = false; + private $autoReferer = false; + private $maxRedirs; + private $withHeader = false; + private $nobody = false; + + /** @var callable */ + private $headerFunction; + /** @var callable */ + private $readFunction; + /** @var callable */ + private $writeFunction; + /** @var callable */ + private $progressFunction; + + public $returnTransfer = false; + public $method = 'GET'; + public $headers = []; + + public $transfer; + + public $errCode = 0; + public $errMsg = ''; + + public function __construct($url = null) + { + if ($url) { + $this->create($url); + } + } + + private function create(string $url): void + { + if (!swoole_string($url)->contains('://')) { + $url = 'http://' . $url; + } + $this->info['url'] = $url; + $info = parse_url($url); + $proto = swoole_array_default_value($info, 'scheme'); + if ($proto != 'http' and $proto != 'https') { + $this->setError(CURLE_UNSUPPORTED_PROTOCOL, "Protocol \"{$proto}\" not supported or disabled in libcurl"); + return; + } + $ssl = $proto === 'https'; + if (empty($info['port'])) { + $port = $ssl ? 443 : 80; + } else { + $port = intval($info['port']); + } + $this->urlInfo = $info; + $this->client = new Client($info['host'], $port, $ssl); + } + + public function execute() + { + $this->info['redirect_count'] = $this->info['starttransfer_time'] = 0; + $this->info['redirect_url'] = ''; + $timeBegin = microtime(true); + /** + * Socket + */ + $client = $this->client; + if (!$client) { + if (!$this->errCode) { + $this->setError(CURLE_URL_MALFORMAT); + } + return false; + } + $isRedirect = false; + do { + if ($isRedirect and !$client) { + $proto = swoole_array_default_value($this->urlInfo, 'scheme'); + if ($proto != 'http' and $proto != 'https') { + $this->setError(CURLE_UNSUPPORTED_PROTOCOL, "Protocol \"{$proto}\" not supported or disabled in libcurl"); + return false; + } + $ssl = $proto === 'https'; + if (empty($this->urlInfo['port'])) { + $port = $ssl ? 443 : 80; + } else { + $port = intval($this->urlInfo['port']); + } + $client = new Client($this->urlInfo['host'], $port, $ssl); + } + /** + * Http Proxy + */ + if ($this->proxy) { + list($proxy_host, $proxy_port) = explode(':', $this->proxy); + if (!filter_var($proxy_host, FILTER_VALIDATE_IP)) { + $ip = Swoole\Coroutine::gethostbyname($proxy_host); + if (!$ip) { + $this->setError(CURLE_COULDNT_RESOLVE_PROXY, 'Could not resolve proxy: ' . $proxy_host); + return false; + } else { + $proxy_host = $ip; + } + } + $client->set(['http_proxy_host' => $proxy_host, 'http_proxy_port' => $proxy_port]); + } + /** + * Client Options + */ + if ($this->clientOptions) { + $client->set($this->clientOptions); + } + $client->setMethod($this->method); + /** + * Upload File + */ + if ($this->postData and is_array($this->postData)) { + foreach ($this->postData as $k => $v) { + if ($v instanceof CURLFile) { + $client->addFile($v->getFilename(), $k, $v->getMimeType() ?: 'application/octet-stream', $v->getPostFilename()); + unset($this->postData[$k]); + } + } + } + /** + * Post Data + */ + if ($this->postData) { + if (is_string($this->postData) and empty($this->headers['Content-Type'])) { + $this->headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + $client->setData($this->postData); + $this->postData = []; + } + /** + * Http Header + */ + $this->headers['Host'] = $this->urlInfo['host'] . (isset($this->urlInfo['port']) ? (':' . $this->urlInfo['port']) : ''); + $client->setHeaders($this->headers); + /** + * Execute + */ + $executeResult = $client->execute($this->getUrl()); + if (!$executeResult) { + $errCode = $client->errCode; + if ($errCode == SWOOLE_ERROR_DNSLOOKUP_RESOLVE_FAILED or $errCode == SWOOLE_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT) { + $this->setError(CURLE_COULDNT_RESOLVE_HOST, 'Could not resolve host: ' . $client->host); + } + $this->info['total_time'] = microtime(true) - $timeBegin; + return false; + } + if ($client->statusCode >= 300 and $client->statusCode < 400 and isset($client->headers['location'])) { + $redirectParsedUrl = $this->getRedirectUrl($client->headers['location']); + $redirectUrl = $this->unparseUrl($redirectParsedUrl); + if ($this->followLocation and (null === $this->maxRedirs or $this->info['redirect_count'] < $this->maxRedirs)) { + $isRedirect = true; + if (0 === $this->info['redirect_count']) { + $this->info['starttransfer_time'] = microtime(true) - $timeBegin; + $redirectBeginTime = microtime(true); + } + // force GET + if (in_array($client->statusCode, [301, 302, 303])) { + $this->method = 'GET'; + } + if ($this->urlInfo['host'] !== $redirectParsedUrl['host'] or ($this->urlInfo['port'] ?? null) !== ($redirectParsedUrl['port'] ?? null) or $this->urlInfo['scheme'] !== $redirectParsedUrl['scheme']) { + // If host, port, and scheme are the same, reuse $client. Otherwise, release the old $client + $client = null; + } + if ($this->autoReferer) { + $this->headers['Referer'] = $this->info['url']; + } + $this->urlInfo = $redirectParsedUrl; + $this->info['url'] = $redirectUrl; + $this->info['redirect_count']++; + } else { + $this->info['redirect_url'] = $redirectUrl; + break; + } + } else { + break; + } + } while (true); + $this->info['total_time'] = microtime(true) - $timeBegin; + $this->info['http_code'] = $client->statusCode; + $this->info['content_type'] = $client->headers['content-type'] ?? ''; + $this->info['size_download'] = $this->info['download_content_length'] = strlen($client->body);; + $this->info['speed_download'] = 1 / $this->info['total_time'] * $this->info['size_download']; + if (isset($redirectBeginTime)) { + $this->info['redirect_time'] = microtime(true) - $redirectBeginTime; + } + + $headerContent = ''; + if ($client->headers) { + $cb = $this->headerFunction; + if ($client->statusCode > 0) { + $row = 'HTTP/1.1 ' . $client->statusCode . ' ' . Swoole\Http\StatusCode::getReasonPhrase($client->statusCode) . "\r\n"; + if ($cb) { + $cb($this, $row); + } + $headerContent .= $row; + } + foreach ($client->headers as $k => $v) { + $row = "$k: $v\r\n"; + if ($cb) { + $cb($this, $row); + } + $headerContent .= $row; + } + $headerContent .= "\r\n"; + $this->info['header_size'] = strlen($headerContent); + if ($cb) { + $cb($this, ''); + } + } else { + $this->info['header_size'] = 0; + } + + if ($client->body and $this->readFunction) { + $cb = $this->readFunction; + $cb($this, $this->outputStream, strlen($client->body)); + } + + if ($this->withHeader) { + $transfer = $headerContent . $client->body; + } else { + $transfer = $client->body; + } + + if ($this->withHeaderOut) { + $headerOutContent = $client->getHeaderOut(); + $this->info['request_header'] = $headerOutContent ? $headerOutContent . "\r\n\r\n" : ''; + } + if ($this->withFileTime) { + if (!empty($client->headers['last-modified'])) { + $this->info['filetime'] = strtotime($client->headers['last-modified']); + } else { + $this->info['filetime'] = -1; + } + } + + if ($this->returnTransfer) { + return $this->transfer = $transfer; + } else { + if ($this->outputStream) { + return fwrite($this->outputStream, $transfer) === strlen($transfer); + } else { + echo $transfer; + } + return true; + } + } + + public function close(): void + { + $this->client = null; + } + + private function setError($code, $msg = ''): void + { + $this->errCode = $code; + $this->errMsg = $msg ? $msg : self::ERRORS[$code]; + } + + private function getUrl(): string + { + if (empty($this->urlInfo['path'])) { + $url = '/'; + } else { + $url = $this->urlInfo['path']; + } + if (!empty($this->urlInfo['query'])) { + $url .= '?' . $this->urlInfo['query']; + } + if (!empty($this->urlInfo['fragment'])) { + $url .= '#' . $this->urlInfo['fragment']; + } + return $url; + } + + /** + * @param int $opt + * @param $value + * @return bool + * @throws Swoole\Curl\Exception + */ + public function setOption(int $opt, $value): bool + { + switch ($opt) { + /** + * Basic + */ + case CURLOPT_URL: + $this->create($value); + break; + case CURLOPT_RETURNTRANSFER: + $this->returnTransfer = $value; + $this->transfer = ''; + break; + case CURLOPT_ENCODING: + if (empty($value)) { + $value = 'gzip'; + } + $this->headers['Accept-Encoding'] = $value; + break; + case CURLOPT_PROXY: + $this->proxy = $value; + break; + case CURLOPT_NOBODY: + $this->nobody = boolval($value); + $this->method = 'HEAD'; + break; + /** + * Ignore options + */ + case CURLOPT_SSLVERSION: + case CURLOPT_NOSIGNAL: + case CURLOPT_FRESH_CONNECT: + /** + * From PHP 5.1.3, this option has no effect: the raw output will always be returned when CURLOPT_RETURNTRANSFER is used. + */ + case CURLOPT_BINARYTRANSFER: + /** + * TODO + */ + case CURLOPT_VERBOSE: + case CURLOPT_DNS_CACHE_TIMEOUT: + break; + /** + * SSL + */ + case CURLOPT_SSL_VERIFYHOST: + break; + case CURLOPT_SSL_VERIFYPEER: + $this->clientOptions['ssl_verify_peer'] = $value; + break; + /** + * Http Post + */ + case CURLOPT_POST: + $this->method = 'POST'; + break; + case CURLOPT_POSTFIELDS: + $this->postData = $value; + $this->method = 'POST'; + break; + /** + * Upload + */ + case CURLOPT_SAFE_UPLOAD: + if (!$value) { + trigger_error('curl_setopt(): Disabling safe uploads is no longer supported', E_USER_WARNING); + } + break; + /** + * Http Header + */ + case CURLOPT_HTTPHEADER: + if (!is_array($value) and !($value instanceof Iterator)) { + trigger_error('swoole_curl_setopt(): You must pass either an object or an array with the CURLOPT_HTTPHEADER argument', E_USER_WARNING); + return false; + } + foreach ($value as $header) { + list($k, $v) = explode(':', $header); + $v = trim($v); + if ($v) { + $this->headers[$k] = $v; + } + } + break; + case CURLOPT_REFERER: + $this->headers['Referer'] = $value; + break; + + case CURLINFO_HEADER_OUT: + $this->withHeaderOut = boolval($value); + break; + + case CURLOPT_FILETIME: + $this->withFileTime = boolval($value); + break; + + case CURLOPT_USERAGENT: + $this->headers['User-Agent'] = $value; + break; + + case CURLOPT_CUSTOMREQUEST: + break; + case CURLOPT_PROTOCOLS: + if ($value > 3) { + throw new Swoole\Curl\Exception("option[{$opt}={$value}] not supported"); + } + break; + case CURLOPT_HTTP_VERSION: + if ($value != CURL_HTTP_VERSION_1_1) { + trigger_error("swoole_curl: http version[{$value}] not supported", E_USER_WARNING); + } + break; + /** + * Http Cookie + */ + case CURLOPT_COOKIE: + $this->headers['Cookie'] = $value; + break; + case CURLOPT_CONNECTTIMEOUT: + $this->clientOptions['connect_timeout'] = $value; + break; + case CURLOPT_CONNECTTIMEOUT_MS: + $this->clientOptions['connect_timeout'] = $value / 1000; + break; + case CURLOPT_TIMEOUT: + $this->clientOptions['timeout'] = $value; + break; + case CURLOPT_TIMEOUT_MS: + $this->clientOptions['timeout'] = $value / 1000; + break; + case CURLOPT_FILE: + $this->outputStream = $value; + break; + case CURLOPT_HEADER: + $this->withHeader = $value; + break; + case CURLOPT_HEADERFUNCTION: + $this->headerFunction = $value; + break; + case CURLOPT_READFUNCTION: + $this->readFunction = $value; + break; + case CURLOPT_WRITEFUNCTION: + $this->writeFunction = $value; + break; + case CURLOPT_PROGRESSFUNCTION: + $this->progressFunction = $value; + break; + case CURLOPT_USERPWD: + $this->headers['Authorization'] = 'Basic ' . base64_encode($value); + break; + case CURLOPT_FOLLOWLOCATION: + $this->followLocation = $value; + break; + case CURLOPT_AUTOREFERER: + $this->autoReferer = $value; + break; + case CURLOPT_MAXREDIRS: + $this->maxRedirs = $value; + break; + default: + throw new Swoole\Curl\Exception("option[{$opt}] not supported"); + } + return true; + } + + public function reset(): void + { + } + + public function getInfo() + { + return $this->info; + } + + private function unparseUrl(array $parsedUrl): string + { + $scheme = ($parsedUrl['scheme'] ?? 'http') . '://'; + $host = $parsedUrl['host'] ?? ''; + $port = isset($parsedUrl['port']) ? ':' . $parsedUrl['port'] : ''; + $user = $parsedUrl['user'] ?? ''; + $pass = isset($parsedUrl['pass']) ? ':' . $parsedUrl['pass'] : ''; + $pass = ($user or $pass) ? "$pass@" : ''; + $path = $parsedUrl['path'] ?? ''; + $query = (isset($parsedUrl['query']) and '' !== $parsedUrl['query']) ? '?' . $parsedUrl['query'] : ''; + $fragment = isset($parsedUrl['fragment']) ? '#' . $parsedUrl['fragment'] : ''; + return $scheme . $user . $pass . $host . $port . $path . $query . $fragment; + } + + private function getRedirectUrl(string $location): array + { + $uri = parse_url($location); + if (isset($uri['host'])) { + $redirectUri = $uri; + } else { + if (!isset($location[0])) { + return []; + } + $redirectUri = $this->urlInfo; + $redirectUri['query'] = ''; + if ('/' === $location[0]) { + $redirectUri['path'] = $location; + } else { + $path = dirname($redirectUri['path'] ?? ''); + if ('.' === $path) { + $path = '/'; + } + if (isset($location[1]) and './' === substr($location, 0, 2)) { + $location = substr($location, 2); + } + $redirectUri['path'] = $path . $location; + } + foreach ($uri as $k => $v) { + if (!in_array($k, ['path', 'query'])) { + $redirectUri[$k] = $v; + } + } + } + return $redirectUri; + } +} \ No newline at end of file diff --git a/vendor/swoole/ide-helper/output/swoole_lib/core/Http/StatusCode.php b/vendor/swoole/ide-helper/output/swoole_lib/core/Http/StatusCode.php new file mode 100644 index 0000000..3241cea --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_lib/core/Http/StatusCode.php @@ -0,0 +1,142 @@ + 'Continue', + self::SWITCHING_PROTOCOLS => 'Switching Protocols', + self::PROCESSING => 'Processing', + self::OK => 'OK', + self::CREATED => 'Created', + self::ACCEPTED => 'Accepted', + self::NON_AUTHORITATIVE_INFORMATION => 'Non-Authoritative Information', + self::NO_CONTENT => 'No Content', + self::RESET_CONTENT => 'Reset Content', + self::PARTIAL_CONTENT => 'Partial Content', + self::MULTI_STATUS => 'Multi-status', + self::ALREADY_REPORTED => 'Already Reported', + self::IM_USED => 'IM Used', + self::MULTIPLE_CHOICES => 'Multiple Choices', + self::MOVED_PERMANENTLY => 'Moved Permanently', + self::FOUND => 'Found', + self::SEE_OTHER => 'See Other', + self::NOT_MODIFIED => 'Not Modified', + self::USE_PROXY => 'Use Proxy', + self::SWITCH_PROXY => 'Switch Proxy', + self::TEMPORARY_REDIRECT => 'Temporary Redirect', + self::PERMANENT_REDIRECT => 'Permanent Redirect', + self::BAD_REQUEST => 'Bad Request', + self::UNAUTHORIZED => 'Unauthorized', + self::PAYMENT_REQUIRED => 'Payment Required', + self::FORBIDDEN => 'Forbidden', + self::NOT_FOUND => 'Not Found', + self::METHOD_NOT_ALLOWED => 'Method Not Allowed', + self::NOT_ACCEPTABLE => 'Not Acceptable', + self::PROXY_AUTHENTICATION_REQUIRED => 'Proxy Authentication Required', + self::REQUEST_TIME_OUT => 'Request Time-out', + self::CONFLICT => 'Conflict', + self::GONE => 'Gone', + self::LENGTH_REQUIRED => 'Length Required', + self::PRECONDITION_FAILED => 'Precondition Failed', + self::REQUEST_ENTITY_TOO_LARGE => 'Request Entity Too Large', + self::REQUEST_URI_TOO_LARGE => 'Request-URI Too Large', + self::UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type', + self::REQUESTED_RANGE_NOT_SATISFIABLE => 'Requested range not satisfiable', + self::EXPECTATION_FAILED => 'Expectation Failed', + self::MISDIRECTED_REQUEST => 'Unprocessable Entity', + self::UNPROCESSABLE_ENTITY => 'Unprocessable Entity', + self::LOCKED => 'Locked', + self::FAILED_DEPENDENCY => 'Failed Dependency', + self::UNORDERED_COLLECTION => 'Unordered Collection', + self::UPGRADE_REQUIRED => 'Upgrade Required', + self::PRECONDITION_REQUIRED => 'Precondition Required', + self::TOO_MANY_REQUESTS => 'Too Many Requests', + self::REQUEST_HEADER_FIELDS_TOO_LARGE => 'Request Header Fields Too Large', + self::UNAVAILABLE_FOR_LEGAL_REASONS => 'Unavailable For Legal Reasons', + self::INTERNAL_SERVER_ERROR => 'Internal Server Error', + self::NOT_IMPLEMENTED => 'Not Implemented', + self::BAD_GATEWAY => 'Bad Gateway', + self::SERVICE_UNAVAILABLE => 'Service Unavailable', + self::GATEWAY_TIME_OUT => 'Gateway Time-out', + self::HTTP_VERSION_NOT_SUPPORTED => 'HTTP Version not supported', + self::VARIANT_ALSO_NEGOTIATES => 'Variant Also Negotiates', + self::INSUFFICIENT_STORAGE => 'Insufficient Storage', + self::LOOP_DETECTED => 'Loop Detected', + self::NOT_EXTENDED => 'Not Extended', + self::NETWORK_AUTHENTICATION_REQUIRED => 'Network Authentication Required' + ]; + + /** + * getReasonPhrase + * @param int $value + * @return string + */ + public static function getReasonPhrase(int $value): string + { + return static::$reasonPhrases[$value] ?? 'Unknown'; + } +} \ No newline at end of file diff --git a/vendor/swoole/ide-helper/output/swoole_lib/core/StringObject.php b/vendor/swoole/ide-helper/output/swoole_lib/core/StringObject.php new file mode 100644 index 0000000..57958b0 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_lib/core/StringObject.php @@ -0,0 +1,237 @@ +string = $string; + } + + /** + * @return int + */ + public function length(): int + { + return strlen($this->string); + } + + /** + * @param string $needle + * @param int $offset + * @return bool|int + */ + public function indexOf(string $needle, int $offset = 0) + { + return strpos($this->string, $needle, $offset); + } + + /** + * @param string $needle + * @param int $offset + * @return bool|int + */ + public function lastIndexOf(string $needle, int $offset = 0) + { + return strrpos($this->string, $needle, $offset); + } + + /** + * @param string $needle + * @param int $offset + * @return bool|int + */ + public function pos(string $needle, int $offset = 0) + { + return strpos($this->string, $needle, $offset); + } + + /** + * @param string $needle + * @param int $offset + * @return bool|int + */ + public function rpos(string $needle, int $offset = 0) + { + return strrpos($this->string, $needle, $offset); + } + + /** + * @param string $needle + * @return bool|int + */ + public function ipos(string $needle) + { + return stripos($this->string, $needle); + } + + /** + * @return static + */ + public function lower(): self + { + return new static(strtolower($this->string)); + } + + /** + * @return static + */ + public function upper(): self + { + return new static(strtoupper($this->string)); + } + + /** + * @return static + */ + public function trim(): self + { + return new static(trim($this->string)); + } + + /** + * @return static + */ + public function lrim(): self + { + return new static(ltrim($this->string)); + } + + /** + * @return static + */ + public function rtrim(): self + { + return new static(rtrim($this->string)); + } + + /** + * @param int $offset + * @param mixed ...$length + * @return static + */ + public function substr(int $offset, ...$length): self + { + return new static(substr($this->string, $offset, ...$length)); + } + + /** + * @param $n + * @return StringObject + */ + public function repeat($n) + { + return new static(str_repeat($this->string, $n)); + } + + /** + * @param string $search + * @param string $replace + * @param null $count + * @return static + */ + public function replace(string $search, string $replace, &$count = null): self + { + return new static(str_replace($search, $replace, $this->string, $count)); + } + + /** + * @param string $needle + * @return bool + */ + public function startsWith(string $needle): bool + { + return strpos($this->string, $needle) === 0; + } + + /** + * @param string $subString + * @return bool + */ + public function contains(string $subString): bool + { + return strpos($this->string, $subString) !== false; + } + + /** + * @param string $needle + * @return bool + */ + public function endsWith(string $needle): bool + { + return strrpos($this->string, $needle) === (strlen($needle) - 1); + } + + /** + * @param string $delimiter + * @param int $limit + * @return ArrayObject + */ + public function split(string $delimiter, int $limit = PHP_INT_MAX): ArrayObject + { + return static::detectArrayType(explode($delimiter, $this->string, $limit)); + } + + /** + * @param int $index + * @return string + */ + public function char(int $index): string + { + return $this->string[$index]; + } + + /** + * @param int $chunkLength + * @param string $chunkEnd + * @return static + */ + public function chunkSplit(int $chunkLength = 1, string $chunkEnd = ''): self + { + return new static(chunk_split($this->string, $chunkLength, $chunkEnd)); + } + + /** + * @param int $splitLength + * @return ArrayObject + */ + public function chunk($splitLength = 1): ArrayObject + { + return static::detectArrayType(str_split($this->string, $splitLength)); + } + + /** + * @return string + */ + public function toString() + { + return $this->string; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->string; + } + + /** + * @param array $value + * @return ArrayObject + */ + protected static function detectArrayType(array $value): ArrayObject + { + return new ArrayObject($value); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_lib/functions.php b/vendor/swoole/ide-helper/output/swoole_lib/functions.php new file mode 100644 index 0000000..6f9059e --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_lib/functions.php @@ -0,0 +1,49 @@ + [], + self::ALIAS_SNAKE_CASE => [], + ]; + + /** + * Methods that don't need to have return type specified. + */ + const IGNORED_METHODS = [ + '__construct' => null, + '__destruct' => null, + ]; + + /** + * AbstractStubGenerator constructor. + * + * @throws Exception + * @throws ReflectionException + */ + public function __construct() + { + $this->init(); + + if (!extension_loaded($this->extension)) { + throw new Exception("Extension $this->extension not enabled or not installed."); + } + + $this->language = 'chinese'; + $this->dirOutput = dirname(__DIR__) . '/output/' . $this->extension; + $this->dirConfig = dirname(__DIR__) . '/config'; + $this->rf_ext = new ReflectionExtension($this->extension); + } + + /** + * @throws Exception + * @throws ReflectionException + */ + public function export(): void + { + // Retrieve and save all constants. + if ($this->rf_ext->getConstants()) { + $defines = ''; + foreach ($this->rf_ext->getConstants() as $name => $value) { + $defines .= sprintf("define('%s', %s);\n", $name, (is_numeric($value) ? $value : "'{$value}'")); + } + $this->writeToPhpFile($this->dirOutput . '/constants.php', $defines); + } + + // Retrieve and save all functions. + $output = $this->getFunctionsDef(); + if (!empty($output)) { + $this->writeToPhpFile($this->dirOutput . '/functions.php', $output); + } + + // Retrieve and save all classes. + $classes = $this->rf_ext->getClasses(); + // There are three types of class names in Swoole: + // 1. short name of a class. Short names start with "Co\", and they can be found in file output/aliases.php. + // 2. fully qualified name (class name with namespace prefix), e.g., \Swoole\Timer. These classes can be found + // under folder output/namespace. + // 3. snake_case. e.g., swoole_timer. These aliases can be found in file output/aliases.php. + foreach ($classes as $className => $ref) { + if (strtolower(substr($className, 0, 3)) == 'co\\') { + $className = str_replace('Swoole\\Coroutine', 'Co', $ref->getName()); + $this->aliases[self::ALIAS_SHORT_NAME][$className] = $ref->getName(); + } elseif (strchr($className, '\\')) { + $this->exportNamespaceClass($className, $ref); + } else { + $this->aliases[self::ALIAS_SNAKE_CASE][$className] = $this->getNamespaceAlias($className); + } + } + + $class_alias = ''; + foreach (array_filter($this->aliases) as $type => $aliases) { + if (!empty($class_alias)) { + $class_alias .= "\n"; + } + asort($aliases); + foreach ($aliases as $alias => $original) { + $class_alias .= "class_alias({$original}::class, {$alias}::class);\n"; + } + } + $this->writeToPhpFile($this->dirOutput . '/aliases.php', $class_alias); + } + + /** + * @return string + */ + public function getVersion(): string + { + return $this->rf_ext->getVersion(); + } + + /** + * @return string + */ + public function getExtension(): string + { + return $this->extension; + } + + /** + * @param string $extension + * @return $this + */ + public function setExtension(string $extension): self + { + $this->extension = $extension; + + return $this; + } + + /** + * @param string $className + * @return string + */ + protected function getNamespaceAlias(string $className): string + { + if (strcasecmp($className, 'co') === 0) { + return Coroutine::class; + } elseif (strcasecmp($className, 'chan') === 0) { + return Channel::class; + } else { + return str_replace('_', '\\', ucwords($className, '_')); + } + } + + /** + * @param string $class + * @param string $name + * @param string $type + * @return array + */ + protected function getConfig(string $class, string $name, string $type): array + { + switch ($type) { + case self::C_CONSTANT: + $dir = 'constant'; + break; + case self::C_METHOD: + $dir = 'method'; + break; + case self::C_PROPERTY: + $dir = 'property'; + break; + default: + return false; + } + $file = $this->dirConfig . '/' . $this->language . '/' . strtolower($class) . '/' . $dir . '/' . $name . '.php'; + if (is_file($file)) { + return include $file; + } else { + return array(); + } + } + + /** + * @param ReflectionParameter $parameter + * @return string|null + */ + protected function getDefaultValue(ReflectionParameter $parameter): ?string + { + try { + $default_value = $parameter->getDefaultValue(); + if ($default_value === []) { + $default_value = '[]'; + } elseif ($default_value === null) { + $default_value = 'null'; + } elseif (is_bool($default_value)) { + $default_value = $default_value ? 'true' : 'false'; + } else { + $default_value = var_export($default_value, true); + } + } catch (\Throwable $e) { + if ($parameter->isOptional()) { + $default_value = 'null'; + } else { + $default_value = null; + } + } + return $default_value; + } + + /** + * @return string + */ + protected function getFunctionsDef(): string + { + $all = ''; + foreach ($this->rf_ext->getFunctions() as $function) { + $vp = array(); + $comment = "/**\n"; + $params = $function->getParameters(); + foreach ($params as $param) { + $default_value = $this->getDefaultValue($param); + $comment .= " * @param \${$param->name}[" . + ($param->isOptional() ? 'optional' : 'required') . + "]\n"; + $vp[] = ($param->isPassedByReference() ? '&' : '') . + "\${$param->name}" . + ($default_value ? " = {$default_value}" : ''); + } + $comment .= " * @return mixed\n"; + $comment .= " */\n"; + $comment .= sprintf("function %s(%s){}\n\n", $function->getName(), join(', ', $vp)); + $all .= $comment; + } + + return $all; + } + + /** + * @param string $classname + * @param ReflectionClass $ref + * @throws Exception + * @throws ReflectionException + */ + protected function exportNamespaceClass(string $classname, ReflectionClass $ref): void + { + (new NamespaceRule($this))->validate($classname); + + $class = ClassGenerator::fromReflection(new ClassReflection($ref->getName())); + foreach ($class->getMethods() as $method) { + if ((null === $method->getReturnType()) && !array_key_exists($method->getName(), self::IGNORED_METHODS)) { + $method->setDocBlock( + DocBlockGenerator::fromArray( + [ + 'shortDescription' => null, + 'longDescription' => null, + 'tags' => [ + new ReturnTag( + [ + 'datatype' => 'mixed', + ] + ), + ], + ] + ) + ); + } + } + + $this->writeToPhpFile( + $this->dirOutput . '/namespace/' . implode('/', array_slice(explode('\\', $classname), 1)) . '.php', + $class->generate() + ); + } + + /** + * @param string $path + * @param string $content + * @return AbstractStubGenerator + */ + protected function writeToPhpFile(string $path, string $content): self + { + $this->mkdir(dirname($path)); + file_put_contents($path, "setGenerator($generator); + } + + /** + * @param mixed ...$params + */ + public function validate(...$params): void + { + if (in_array($this->getGenerator()->getExtension(), $this->getEnabledExtensions())) { + $this->validateWith(...$params); + } + } + + /** + * @return AbstractStubGenerator + */ + protected function getGenerator(): AbstractStubGenerator + { + return $this->generator; + } + + /** + * @param AbstractStubGenerator $generator + * @return $this + */ + protected function setGenerator(AbstractStubGenerator $generator): self + { + $this->generator = $generator; + + return $this; + } + + /** + * @param mixed ...$params + * @throws Exception + */ + abstract protected function validateWith(...$params): void; + + /** + * @return array + */ + abstract protected function getEnabledExtensions(): array; +} diff --git a/vendor/swoole/ide-helper/src/Rules/NamespaceRule.php b/vendor/swoole/ide-helper/src/Rules/NamespaceRule.php new file mode 100644 index 0000000..049f900 --- /dev/null +++ b/vendor/swoole/ide-helper/src/Rules/NamespaceRule.php @@ -0,0 +1,36 @@ +getGenerator()->getExtension()) !== 0) { + throw new Exception( + "Class $params[0] should be under namespace \\{$this->getGenerator()->getExtension()} but not." + ); + } + } + + /** + * @inheritDoc + */ + protected function getEnabledExtensions(): array + { + return [ + Constant::EXT_SWOOLE, + ]; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/Swoole.php b/vendor/swoole/ide-helper/src/StubGenerators/Swoole.php new file mode 100644 index 0000000..54ad31e --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/Swoole.php @@ -0,0 +1,24 @@ +extension = Constant::EXT_SWOOLE; + + return $this; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/SwooleAsync.php b/vendor/swoole/ide-helper/src/StubGenerators/SwooleAsync.php new file mode 100644 index 0000000..d95fada --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/SwooleAsync.php @@ -0,0 +1,24 @@ +extension = Constant::EXT_SWOOLE_ASYNC; + + return $this; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/SwooleLib.php b/vendor/swoole/ide-helper/src/StubGenerators/SwooleLib.php new file mode 100644 index 0000000..eede24c --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/SwooleLib.php @@ -0,0 +1,80 @@ +extension = "swoole_lib"; + $this->dirOutput = dirname($this->dirOutput) . DIRECTORY_SEPARATOR . $this->extension; + } + + /** + * @inheritDoc + */ + public function export(): void + { + $this->mkdir($this->dirOutput); + + $fileSystem = new Filesystem(); + $fileSystem->mirror($this->libDir, $this->dirOutput); + foreach (self::EXTRA_FILES as $file) { + $fileSystem->remove($this->dirOutput . DIRECTORY_SEPARATOR . $file); + } + } + + /** + * @inheritDoc + * @throws Exception + */ + protected function init(): AbstractStubGenerator + { + $this->extension = Constant::EXT_SWOOLE; + + if (!empty($_SERVER["SWOOLE_LIB_DIR"])) { + $this->libDir = $_SERVER["SWOOLE_LIB_DIR"]; + } elseif (!empty($_SERVER["SWOOLE_SRC_DIR"])) { + $this->libDir = $_SERVER["SWOOLE_SRC_DIR"] . DIRECTORY_SEPARATOR . "library"; + } + + if (empty($this->libDir)) { + throw new Exception("Swoole library directory is not specified."); + } elseif (!is_dir($this->libDir)) { + throw new Exception("Swoole library directory \"{$this->libDir}\" is invalid."); + } + + return $this; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/SwooleOrm.php b/vendor/swoole/ide-helper/src/StubGenerators/SwooleOrm.php new file mode 100644 index 0000000..899265c --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/SwooleOrm.php @@ -0,0 +1,24 @@ +extension = Constant::EXT_SWOOLE_ORM; + + return $this; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/SwoolePostgresql.php b/vendor/swoole/ide-helper/src/StubGenerators/SwoolePostgresql.php new file mode 100644 index 0000000..06fde90 --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/SwoolePostgresql.php @@ -0,0 +1,24 @@ +extension = Constant::EXT_SWOOLE_POSTGRESQL; + + return $this; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/SwooleSerialize.php b/vendor/swoole/ide-helper/src/StubGenerators/SwooleSerialize.php new file mode 100644 index 0000000..6764161 --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/SwooleSerialize.php @@ -0,0 +1,24 @@ +extension = Constant::EXT_SWOOLE_SERIALIZE; + + return $this; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/SwooleZookeeper.php b/vendor/swoole/ide-helper/src/StubGenerators/SwooleZookeeper.php new file mode 100644 index 0000000..e13c715 --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/SwooleZookeeper.php @@ -0,0 +1,24 @@ +extension = Constant::EXT_SWOOLE_ZOOKEEPER; + + return $this; + } +} diff --git a/vendor/symfony/finder/.gitattributes b/vendor/symfony/finder/.gitattributes new file mode 100644 index 0000000..ebb9287 --- /dev/null +++ b/vendor/symfony/finder/.gitattributes @@ -0,0 +1,3 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitignore export-ignore diff --git a/vendor/symfony/finder/CHANGELOG.md b/vendor/symfony/finder/CHANGELOG.md new file mode 100644 index 0000000..2045184 --- /dev/null +++ b/vendor/symfony/finder/CHANGELOG.md @@ -0,0 +1,74 @@ +CHANGELOG +========= + +4.3.0 +----- + + * added Finder::ignoreVCSIgnored() to ignore files based on rules listed in .gitignore + +4.2.0 +----- + + * added $useNaturalSort option to Finder::sortByName() method + * the `Finder::sortByName()` method will have a new `$useNaturalSort` + argument in version 5.0, not defining it is deprecated + * added `Finder::reverseSorting()` to reverse the sorting + +4.0.0 +----- + + * removed `ExceptionInterface` + * removed `Symfony\Component\Finder\Iterator\FilterIterator` + +3.4.0 +----- + + * deprecated `Symfony\Component\Finder\Iterator\FilterIterator` + * added Finder::hasResults() method to check if any results were found + +3.3.0 +----- + + * added double-star matching to Glob::toRegex() + +3.0.0 +----- + + * removed deprecated classes + +2.8.0 +----- + + * deprecated adapters and related classes + +2.5.0 +----- + * added support for GLOB_BRACE in the paths passed to Finder::in() + +2.3.0 +----- + + * added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs()) + * unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception + +2.2.0 +----- + + * added Finder::path() and Finder::notPath() methods + * added finder adapters to improve performance on specific platforms + * added support for wildcard characters (glob patterns) in the paths passed + to Finder::in() + +2.1.0 +----- + + * added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and + Finder::sortByModifiedTime() + * added Countable to Finder + * added support for an array of directories as an argument to + Finder::exclude() + * added searching based on the file content via Finder::contains() and + Finder::notContains() + * added support for the != operator in the Comparator + * [BC BREAK] filter expressions (used for file name and content) are no more + considered as regexps but glob patterns when they are enclosed in '*' or '?' diff --git a/vendor/symfony/finder/Comparator/Comparator.php b/vendor/symfony/finder/Comparator/Comparator.php new file mode 100644 index 0000000..6aee21c --- /dev/null +++ b/vendor/symfony/finder/Comparator/Comparator.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 Symfony\Component\Finder\Comparator; + +/** + * Comparator. + * + * @author Fabien Potencier + */ +class Comparator +{ + private $target; + private $operator = '=='; + + /** + * Gets the target value. + * + * @return string The target value + */ + public function getTarget() + { + return $this->target; + } + + /** + * Sets the target value. + * + * @param string $target The target value + */ + public function setTarget($target) + { + $this->target = $target; + } + + /** + * Gets the comparison operator. + * + * @return string The operator + */ + public function getOperator() + { + return $this->operator; + } + + /** + * Sets the comparison operator. + * + * @param string $operator A valid operator + * + * @throws \InvalidArgumentException + */ + public function setOperator($operator) + { + if (!$operator) { + $operator = '=='; + } + + if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + } + + $this->operator = $operator; + } + + /** + * Tests against the target. + * + * @param mixed $test A test value + * + * @return bool + */ + public function test($test) + { + switch ($this->operator) { + case '>': + return $test > $this->target; + case '>=': + return $test >= $this->target; + case '<': + return $test < $this->target; + case '<=': + return $test <= $this->target; + case '!=': + return $test != $this->target; + } + + return $test == $this->target; + } +} diff --git a/vendor/symfony/finder/Comparator/DateComparator.php b/vendor/symfony/finder/Comparator/DateComparator.php new file mode 100644 index 0000000..d17c77a --- /dev/null +++ b/vendor/symfony/finder/Comparator/DateComparator.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * DateCompare compiles date comparisons. + * + * @author Fabien Potencier + */ +class DateComparator extends Comparator +{ + /** + * @param string $test A comparison string + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(string $test) + { + if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); + } + + try { + $date = new \DateTime($matches[2]); + $target = $date->format('U'); + } catch (\Exception $e) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); + } + + $operator = isset($matches[1]) ? $matches[1] : '=='; + if ('since' === $operator || 'after' === $operator) { + $operator = '>'; + } + + if ('until' === $operator || 'before' === $operator) { + $operator = '<'; + } + + $this->setOperator($operator); + $this->setTarget($target); + } +} diff --git a/vendor/symfony/finder/Comparator/NumberComparator.php b/vendor/symfony/finder/Comparator/NumberComparator.php new file mode 100644 index 0000000..80667c9 --- /dev/null +++ b/vendor/symfony/finder/Comparator/NumberComparator.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * NumberComparator compiles a simple comparison to an anonymous + * subroutine, which you can call with a value to be tested again. + * + * Now this would be very pointless, if NumberCompare didn't understand + * magnitudes. + * + * The target value may use magnitudes of kilobytes (k, ki), + * megabytes (m, mi), or gigabytes (g, gi). Those suffixed + * with an i use the appropriate 2**n version in accordance with the + * IEC standard: http://physics.nist.gov/cuu/Units/binary.html + * + * Based on the Perl Number::Compare module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + * + * @see http://physics.nist.gov/cuu/Units/binary.html + */ +class NumberComparator extends Comparator +{ + /** + * @param string|int $test A comparison string or an integer + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(?string $test) + { + if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test)); + } + + $target = $matches[2]; + if (!is_numeric($target)) { + throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); + } + if (isset($matches[3])) { + // magnitude + switch (strtolower($matches[3])) { + case 'k': + $target *= 1000; + break; + case 'ki': + $target *= 1024; + break; + case 'm': + $target *= 1000000; + break; + case 'mi': + $target *= 1024 * 1024; + break; + case 'g': + $target *= 1000000000; + break; + case 'gi': + $target *= 1024 * 1024 * 1024; + break; + } + } + + $this->setTarget($target); + $this->setOperator(isset($matches[1]) ? $matches[1] : '=='); + } +} diff --git a/vendor/symfony/finder/Exception/AccessDeniedException.php b/vendor/symfony/finder/Exception/AccessDeniedException.php new file mode 100644 index 0000000..ee195ea --- /dev/null +++ b/vendor/symfony/finder/Exception/AccessDeniedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Jean-François Simon + */ +class AccessDeniedException extends \UnexpectedValueException +{ +} diff --git a/vendor/symfony/finder/Exception/DirectoryNotFoundException.php b/vendor/symfony/finder/Exception/DirectoryNotFoundException.php new file mode 100644 index 0000000..c6cc0f2 --- /dev/null +++ b/vendor/symfony/finder/Exception/DirectoryNotFoundException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Andreas Erhard + */ +class DirectoryNotFoundException extends \InvalidArgumentException +{ +} diff --git a/vendor/symfony/finder/Finder.php b/vendor/symfony/finder/Finder.php new file mode 100644 index 0000000..ecc3515 --- /dev/null +++ b/vendor/symfony/finder/Finder.php @@ -0,0 +1,808 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +use Symfony\Component\Finder\Comparator\DateComparator; +use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Exception\DirectoryNotFoundException; +use Symfony\Component\Finder\Iterator\CustomFilterIterator; +use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; +use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; +use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; +use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; +use Symfony\Component\Finder\Iterator\FilenameFilterIterator; +use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; +use Symfony\Component\Finder\Iterator\SortableIterator; + +/** + * Finder allows to build rules to find files and directories. + * + * It is a thin wrapper around several specialized iterator classes. + * + * All rules may be invoked several times. + * + * All methods return the current Finder object to allow chaining: + * + * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); + * + * @author Fabien Potencier + */ +class Finder implements \IteratorAggregate, \Countable +{ + const IGNORE_VCS_FILES = 1; + const IGNORE_DOT_FILES = 2; + const IGNORE_VCS_IGNORED_FILES = 4; + + private $mode = 0; + private $names = []; + private $notNames = []; + private $exclude = []; + private $filters = []; + private $depths = []; + private $sizes = []; + private $followLinks = false; + private $reverseSorting = false; + private $sort = false; + private $ignore = 0; + private $dirs = []; + private $dates = []; + private $iterators = []; + private $contains = []; + private $notContains = []; + private $paths = []; + private $notPaths = []; + private $ignoreUnreadableDirs = false; + + private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; + + public function __construct() + { + $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + } + + /** + * Creates a new Finder. + * + * @return static + */ + public static function create() + { + return new static(); + } + + /** + * Restricts the matching to directories only. + * + * @return $this + */ + public function directories() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; + + return $this; + } + + /** + * Restricts the matching to files only. + * + * @return $this + */ + public function files() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; + + return $this; + } + + /** + * Adds tests for the directory depth. + * + * Usage: + * + * $finder->depth('> 1') // the Finder will start matching at level 1. + * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. + * $finder->depth(['>= 1', '< 3']) + * + * @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels + * + * @return $this + * + * @see DepthRangeFilterIterator + * @see NumberComparator + */ + public function depth($levels) + { + foreach ((array) $levels as $level) { + $this->depths[] = new Comparator\NumberComparator($level); + } + + return $this; + } + + /** + * Adds tests for file dates (last modified). + * + * The date must be something that strtotime() is able to parse: + * + * $finder->date('since yesterday'); + * $finder->date('until 2 days ago'); + * $finder->date('> now - 2 hours'); + * $finder->date('>= 2005-10-15'); + * $finder->date(['>= 2005-10-15', '<= 2006-05-27']); + * + * @param string|string[] $dates A date range string or an array of date ranges + * + * @return $this + * + * @see strtotime + * @see DateRangeFilterIterator + * @see DateComparator + */ + public function date($dates) + { + foreach ((array) $dates as $date) { + $this->dates[] = new Comparator\DateComparator($date); + } + + return $this; + } + + /** + * Adds rules that files must match. + * + * You can use patterns (delimited with / sign), globs or simple strings. + * + * $finder->name('*.php') + * $finder->name('/\.php$/') // same as above + * $finder->name('test.php') + * $finder->name(['test.py', 'test.php']) + * + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function name($patterns) + { + $this->names = array_merge($this->names, (array) $patterns); + + return $this; + } + + /** + * Adds rules that files must not match. + * + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notName($patterns) + { + $this->notNames = array_merge($this->notNames, (array) $patterns); + + return $this; + } + + /** + * Adds tests that file contents must match. + * + * Strings or PCRE patterns can be used: + * + * $finder->contains('Lorem ipsum') + * $finder->contains('/Lorem ipsum/i') + * $finder->contains(['dolor', '/ipsum/i']) + * + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function contains($patterns) + { + $this->contains = array_merge($this->contains, (array) $patterns); + + return $this; + } + + /** + * Adds tests that file contents must not match. + * + * Strings or PCRE patterns can be used: + * + * $finder->notContains('Lorem ipsum') + * $finder->notContains('/Lorem ipsum/i') + * $finder->notContains(['lorem', '/dolor/i']) + * + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function notContains($patterns) + { + $this->notContains = array_merge($this->notContains, (array) $patterns); + + return $this; + } + + /** + * Adds rules that filenames must match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->path('some/special/dir') + * $finder->path('/some\/special\/dir/') // same as above + * $finder->path(['some dir', 'another/dir']) + * + * Use only / as dirname separator. + * + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function path($patterns) + { + $this->paths = array_merge($this->paths, (array) $patterns); + + return $this; + } + + /** + * Adds rules that filenames must not match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->notPath('some/special/dir') + * $finder->notPath('/some\/special\/dir/') // same as above + * $finder->notPath(['some/file.txt', 'another/file.log']) + * + * Use only / as dirname separator. + * + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notPath($patterns) + { + $this->notPaths = array_merge($this->notPaths, (array) $patterns); + + return $this; + } + + /** + * Adds tests for file sizes. + * + * $finder->size('> 10K'); + * $finder->size('<= 1Ki'); + * $finder->size(4); + * $finder->size(['> 10K', '< 20K']) + * + * @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges + * + * @return $this + * + * @see SizeRangeFilterIterator + * @see NumberComparator + */ + public function size($sizes) + { + foreach ((array) $sizes as $size) { + $this->sizes[] = new Comparator\NumberComparator($size); + } + + return $this; + } + + /** + * Excludes directories. + * + * Directories passed as argument must be relative to the ones defined with the `in()` method. For example: + * + * $finder->in(__DIR__)->exclude('ruby'); + * + * @param string|array $dirs A directory path or an array of directories + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function exclude($dirs) + { + $this->exclude = array_merge($this->exclude, (array) $dirs); + + return $this; + } + + /** + * Excludes "hidden" directories and files (starting with a dot). + * + * This option is enabled by default. + * + * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreDotFiles($ignoreDotFiles) + { + if ($ignoreDotFiles) { + $this->ignore |= static::IGNORE_DOT_FILES; + } else { + $this->ignore &= ~static::IGNORE_DOT_FILES; + } + + return $this; + } + + /** + * Forces the finder to ignore version control directories. + * + * This option is enabled by default. + * + * @param bool $ignoreVCS Whether to exclude VCS files or not + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreVCS($ignoreVCS) + { + if ($ignoreVCS) { + $this->ignore |= static::IGNORE_VCS_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_FILES; + } + + return $this; + } + + /** + * Forces Finder to obey .gitignore and ignore files based on rules listed there. + * + * This option is disabled by default. + * + * @return $this + */ + public function ignoreVCSIgnored(bool $ignoreVCSIgnored) + { + if ($ignoreVCSIgnored) { + $this->ignore |= static::IGNORE_VCS_IGNORED_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES; + } + + return $this; + } + + /** + * Adds VCS patterns. + * + * @see ignoreVCS() + * + * @param string|string[] $pattern VCS patterns to ignore + */ + public static function addVCSPattern($pattern) + { + foreach ((array) $pattern as $p) { + self::$vcsPatterns[] = $p; + } + + self::$vcsPatterns = array_unique(self::$vcsPatterns); + } + + /** + * Sorts files and directories by an anonymous function. + * + * The anonymous function receives two \SplFileInfo instances to compare. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sort(\Closure $closure) + { + $this->sort = $closure; + + return $this; + } + + /** + * Sorts files and directories by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @param bool $useNaturalSort Whether to use natural sort or not, disabled by default + * + * @return $this + * + * @see SortableIterator + */ + public function sortByName(/* bool $useNaturalSort = false */) + { + if (\func_num_args() < 1 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface) { + @trigger_error(sprintf('The "%s()" method will have a new "bool $useNaturalSort = false" argument in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED); + } + $useNaturalSort = 0 < \func_num_args() && func_get_arg(0); + + $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; + + return $this; + } + + /** + * Sorts files and directories by type (directories before files), then by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByType() + { + $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; + + return $this; + } + + /** + * Sorts files and directories by the last accessed time. + * + * This is the time that the file was last accessed, read or written to. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByAccessedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; + + return $this; + } + + /** + * Reverses the sorting. + * + * @return $this + */ + public function reverseSorting() + { + $this->reverseSorting = true; + + return $this; + } + + /** + * Sorts files and directories by the last inode changed time. + * + * This is the time that the inode information was last modified (permissions, owner, group or other metadata). + * + * On Windows, since inode is not available, changed time is actually the file creation time. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByChangedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; + + return $this; + } + + /** + * Sorts files and directories by the last modified time. + * + * This is the last time the actual contents of the file were last modified. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByModifiedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; + + return $this; + } + + /** + * Filters the iterator with an anonymous function. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @return $this + * + * @see CustomFilterIterator + */ + public function filter(\Closure $closure) + { + $this->filters[] = $closure; + + return $this; + } + + /** + * Forces the following of symlinks. + * + * @return $this + */ + public function followLinks() + { + $this->followLinks = true; + + return $this; + } + + /** + * Tells finder to ignore unreadable directories. + * + * By default, scanning unreadable directories content throws an AccessDeniedException. + * + * @param bool $ignore + * + * @return $this + */ + public function ignoreUnreadableDirs($ignore = true) + { + $this->ignoreUnreadableDirs = (bool) $ignore; + + return $this; + } + + /** + * Searches files and directories which match defined rules. + * + * @param string|string[] $dirs A directory path or an array of directories + * + * @return $this + * + * @throws DirectoryNotFoundException if one of the directories does not exist + */ + public function in($dirs) + { + $resolvedDirs = []; + + foreach ((array) $dirs as $dir) { + if (is_dir($dir)) { + $resolvedDirs[] = $this->normalizeDir($dir); + } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR | GLOB_NOSORT)) { + sort($glob); + $resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $glob)); + } else { + throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); + } + } + + $this->dirs = array_merge($this->dirs, $resolvedDirs); + + return $this; + } + + /** + * Returns an Iterator for the current Finder configuration. + * + * This method implements the IteratorAggregate interface. + * + * @return \Iterator|SplFileInfo[] An iterator + * + * @throws \LogicException if the in() method has not been called + */ + public function getIterator() + { + if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { + throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); + } + + if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { + return $this->searchInDirectory($this->dirs[0]); + } + + $iterator = new \AppendIterator(); + foreach ($this->dirs as $dir) { + $iterator->append($this->searchInDirectory($dir)); + } + + foreach ($this->iterators as $it) { + $iterator->append($it); + } + + return $iterator; + } + + /** + * Appends an existing set of files/directories to the finder. + * + * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. + * + * @param iterable $iterator + * + * @return $this + * + * @throws \InvalidArgumentException when the given argument is not iterable + */ + public function append($iterator) + { + if ($iterator instanceof \IteratorAggregate) { + $this->iterators[] = $iterator->getIterator(); + } elseif ($iterator instanceof \Iterator) { + $this->iterators[] = $iterator; + } elseif ($iterator instanceof \Traversable || \is_array($iterator)) { + $it = new \ArrayIterator(); + foreach ($iterator as $file) { + $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file)); + } + $this->iterators[] = $it; + } else { + throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); + } + + return $this; + } + + /** + * Check if the any results were found. + * + * @return bool + */ + public function hasResults() + { + foreach ($this->getIterator() as $_) { + return true; + } + + return false; + } + + /** + * Counts all the results collected by the iterators. + * + * @return int + */ + public function count() + { + return iterator_count($this->getIterator()); + } + + private function searchInDirectory(string $dir): \Iterator + { + $exclude = $this->exclude; + $notPaths = $this->notPaths; + + if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { + $exclude = array_merge($exclude, self::$vcsPatterns); + } + + if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { + $notPaths[] = '#(^|/)\..+(/|$)#'; + } + + if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) { + $gitignoreFilePath = sprintf('%s/.gitignore', $dir); + if (!is_readable($gitignoreFilePath)) { + throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath)); + } + $notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]); + } + + $minDepth = 0; + $maxDepth = PHP_INT_MAX; + + foreach ($this->depths as $comparator) { + switch ($comparator->getOperator()) { + case '>': + $minDepth = $comparator->getTarget() + 1; + break; + case '>=': + $minDepth = $comparator->getTarget(); + break; + case '<': + $maxDepth = $comparator->getTarget() - 1; + break; + case '<=': + $maxDepth = $comparator->getTarget(); + break; + default: + $minDepth = $maxDepth = $comparator->getTarget(); + } + } + + $flags = \RecursiveDirectoryIterator::SKIP_DOTS; + + if ($this->followLinks) { + $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + } + + $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); + + if ($exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude); + } + + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + if ($minDepth > 0 || $maxDepth < PHP_INT_MAX) { + $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); + } + + if ($this->mode) { + $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); + } + + if ($this->names || $this->notNames) { + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->contains || $this->notContains) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->sizes) { + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->dates) { + $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if ($this->paths || $notPaths) { + $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths); + } + + if ($this->sort || $this->reverseSorting) { + $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting); + $iterator = $iteratorAggregate->getIterator(); + } + + return $iterator; + } + + /** + * Normalizes given directory names by removing trailing slashes. + * + * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper + */ + private function normalizeDir(string $dir): string + { + $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR); + + if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) { + $dir .= '/'; + } + + return $dir; + } +} diff --git a/vendor/symfony/finder/Gitignore.php b/vendor/symfony/finder/Gitignore.php new file mode 100644 index 0000000..5ffe585 --- /dev/null +++ b/vendor/symfony/finder/Gitignore.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Gitignore matches against text. + * + * @author Ahmed Abdou + */ +class Gitignore +{ + /** + * Returns a regexp which is the equivalent of the gitignore pattern. + * + * @return string The regexp + */ + public static function toRegex(string $gitignoreFileContent): string + { + $gitignoreFileContent = preg_replace('/^[^\\\r\n]*#.*/m', '', $gitignoreFileContent); + $gitignoreLines = preg_split('/\r\n|\r|\n/', $gitignoreFileContent); + $gitignoreLines = array_map('trim', $gitignoreLines); + $gitignoreLines = array_filter($gitignoreLines); + + $ignoreLinesPositive = array_filter($gitignoreLines, function (string $line) { + return !preg_match('/^!/', $line); + }); + + $ignoreLinesNegative = array_filter($gitignoreLines, function (string $line) { + return preg_match('/^!/', $line); + }); + + $ignoreLinesNegative = array_map(function (string $line) { + return preg_replace('/^!(.*)/', '${1}', $line); + }, $ignoreLinesNegative); + $ignoreLinesNegative = array_map([__CLASS__, 'getRegexFromGitignore'], $ignoreLinesNegative); + + $ignoreLinesPositive = array_map([__CLASS__, 'getRegexFromGitignore'], $ignoreLinesPositive); + if (empty($ignoreLinesPositive)) { + return '/^$/'; + } + + if (empty($ignoreLinesNegative)) { + return sprintf('/%s/', implode('|', $ignoreLinesPositive)); + } + + return sprintf('/(?=^(?:(?!(%s)).)*$)(%s)/', implode('|', $ignoreLinesNegative), implode('|', $ignoreLinesPositive)); + } + + private static function getRegexFromGitignore(string $gitignorePattern): string + { + $regex = '('; + if (0 === strpos($gitignorePattern, '/')) { + $gitignorePattern = substr($gitignorePattern, 1); + $regex .= '^'; + } else { + $regex .= '(^|\/)'; + } + + if ('/' === $gitignorePattern[\strlen($gitignorePattern) - 1]) { + $gitignorePattern = substr($gitignorePattern, 0, -1); + } + + $iMax = \strlen($gitignorePattern); + for ($i = 0; $i < $iMax; ++$i) { + $doubleChars = substr($gitignorePattern, $i, 2); + if ('**' === $doubleChars) { + $regex .= '.+'; + ++$i; + continue; + } + + $c = $gitignorePattern[$i]; + switch ($c) { + case '*': + $regex .= '[^\/]+'; + break; + case '/': + case '.': + case ':': + case '(': + case ')': + case '{': + case '}': + $regex .= '\\'.$c; + break; + default: + $regex .= $c; + } + } + + $regex .= '($|\/)'; + $regex .= ')'; + + return $regex; + } +} diff --git a/vendor/symfony/finder/Glob.php b/vendor/symfony/finder/Glob.php new file mode 100644 index 0000000..ea76d51 --- /dev/null +++ b/vendor/symfony/finder/Glob.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 Symfony\Component\Finder; + +/** + * Glob matches globbing patterns against text. + * + * if match_glob("foo.*", "foo.bar") echo "matched\n"; + * + * // prints foo.bar and foo.baz + * $regex = glob_to_regex("foo.*"); + * for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t) + * { + * if (/$regex/) echo "matched: $car\n"; + * } + * + * Glob implements glob(3) style matching that can be used to match + * against text, rather than fetching names from a filesystem. + * + * Based on the Perl Text::Glob module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + */ +class Glob +{ + /** + * Returns a regexp which is the equivalent of the glob pattern. + * + * @param string $glob The glob pattern + * @param bool $strictLeadingDot + * @param bool $strictWildcardSlash + * @param string $delimiter Optional delimiter + * + * @return string regex The regexp + */ + public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true, $delimiter = '#') + { + $firstByte = true; + $escaping = false; + $inCurlies = 0; + $regex = ''; + $sizeGlob = \strlen($glob); + for ($i = 0; $i < $sizeGlob; ++$i) { + $car = $glob[$i]; + if ($firstByte && $strictLeadingDot && '.' !== $car) { + $regex .= '(?=[^\.])'; + } + + $firstByte = '/' === $car; + + if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) { + $car = '[^/]++/'; + if (!isset($glob[$i + 3])) { + $car .= '?'; + } + + if ($strictLeadingDot) { + $car = '(?=[^\.])'.$car; + } + + $car = '/(?:'.$car.')*'; + $i += 2 + isset($glob[$i + 3]); + + if ('/' === $delimiter) { + $car = str_replace('/', '\\/', $car); + } + } + + if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { + $regex .= "\\$car"; + } elseif ('*' === $car) { + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + } elseif ('?' === $car) { + $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); + } elseif ('{' === $car) { + $regex .= $escaping ? '\\{' : '('; + if (!$escaping) { + ++$inCurlies; + } + } elseif ('}' === $car && $inCurlies) { + $regex .= $escaping ? '}' : ')'; + if (!$escaping) { + --$inCurlies; + } + } elseif (',' === $car && $inCurlies) { + $regex .= $escaping ? ',' : '|'; + } elseif ('\\' === $car) { + if ($escaping) { + $regex .= '\\\\'; + $escaping = false; + } else { + $escaping = true; + } + + continue; + } else { + $regex .= $car; + } + $escaping = false; + } + + return $delimiter.'^'.$regex.'$'.$delimiter; + } +} diff --git a/vendor/symfony/finder/Iterator/CustomFilterIterator.php b/vendor/symfony/finder/Iterator/CustomFilterIterator.php new file mode 100644 index 0000000..a30bbd0 --- /dev/null +++ b/vendor/symfony/finder/Iterator/CustomFilterIterator.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * CustomFilterIterator filters files by applying anonymous functions. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @author Fabien Potencier + */ +class CustomFilterIterator extends \FilterIterator +{ + private $filters = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param callable[] $filters An array of PHP callbacks + * + * @throws \InvalidArgumentException + */ + public function __construct(\Iterator $iterator, array $filters) + { + foreach ($filters as $filter) { + if (!\is_callable($filter)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + } + $this->filters = $filters; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + + foreach ($this->filters as $filter) { + if (false === $filter($fileinfo)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php b/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php new file mode 100644 index 0000000..2e97e00 --- /dev/null +++ b/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\DateComparator; + +/** + * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). + * + * @author Fabien Potencier + */ +class DateRangeFilterIterator extends \FilterIterator +{ + private $comparators = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param DateComparator[] $comparators An array of DateComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + + if (!file_exists($fileinfo->getPathname())) { + return false; + } + + $filedate = $fileinfo->getMTime(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filedate)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php b/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php new file mode 100644 index 0000000..436a66d --- /dev/null +++ b/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.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 Symfony\Component\Finder\Iterator; + +/** + * DepthRangeFilterIterator limits the directory depth. + * + * @author Fabien Potencier + */ +class DepthRangeFilterIterator extends \FilterIterator +{ + private $minDepth = 0; + + /** + * @param \RecursiveIteratorIterator $iterator The Iterator to filter + * @param int $minDepth The min depth + * @param int $maxDepth The max depth + */ + public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = PHP_INT_MAX) + { + $this->minDepth = $minDepth; + $iterator->setMaxDepth(PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + return $this->getInnerIterator()->getDepth() >= $this->minDepth; + } +} diff --git a/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php new file mode 100644 index 0000000..6a1b291 --- /dev/null +++ b/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * ExcludeDirectoryFilterIterator filters out directories. + * + * @author Fabien Potencier + */ +class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator +{ + private $iterator; + private $isRecursive; + private $excludedDirs = []; + private $excludedPattern; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param string[] $directories An array of directories to exclude + */ + public function __construct(\Iterator $iterator, array $directories) + { + $this->iterator = $iterator; + $this->isRecursive = $iterator instanceof \RecursiveIterator; + $patterns = []; + foreach ($directories as $directory) { + $directory = rtrim($directory, '/'); + if (!$this->isRecursive || false !== strpos($directory, '/')) { + $patterns[] = preg_quote($directory, '#'); + } else { + $this->excludedDirs[$directory] = true; + } + } + if ($patterns) { + $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool True if the value should be kept, false otherwise + */ + public function accept() + { + if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { + return false; + } + + if ($this->excludedPattern) { + $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); + $path = str_replace('\\', '/', $path); + + return !preg_match($this->excludedPattern, $path); + } + + return true; + } + + /** + * @return bool + */ + public function hasChildren() + { + return $this->isRecursive && $this->iterator->hasChildren(); + } + + public function getChildren() + { + $children = new self($this->iterator->getChildren(), []); + $children->excludedDirs = $this->excludedDirs; + $children->excludedPattern = $this->excludedPattern; + + return $children; + } +} diff --git a/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php new file mode 100644 index 0000000..a4c4eec --- /dev/null +++ b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FileTypeFilterIterator only keeps files, directories, or both. + * + * @author Fabien Potencier + */ +class FileTypeFilterIterator extends \FilterIterator +{ + const ONLY_FILES = 1; + const ONLY_DIRECTORIES = 2; + + private $mode; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + */ + public function __construct(\Iterator $iterator, int $mode) + { + $this->mode = $mode; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { + return false; + } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { + return false; + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php new file mode 100644 index 0000000..81594b8 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + */ +class FilecontentFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + if (!$this->matchRegexps && !$this->noMatchRegexps) { + return true; + } + + $fileinfo = $this->current(); + + if ($fileinfo->isDir() || !$fileinfo->isReadable()) { + return false; + } + + $content = $fileinfo->getContents(); + if (!$content) { + return false; + } + + return $this->isAccepted($content); + } + + /** + * Converts string to regexp if necessary. + * + * @param string $str Pattern: string or regexp + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Iterator/FilenameFilterIterator.php b/vendor/symfony/finder/Iterator/FilenameFilterIterator.php new file mode 100644 index 0000000..e168cd8 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilenameFilterIterator.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 Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Glob; + +/** + * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). + * + * @author Fabien Potencier + */ +class FilenameFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + return $this->isAccepted($this->current()->getFilename()); + } + + /** + * Converts glob to regexp. + * + * PCRE patterns are left unchanged. + * Glob strings are transformed with Glob::toRegex(). + * + * @param string $str Pattern: glob or regexp + * + * @return string regexp corresponding to a given glob or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : Glob::toRegex($str); + } +} diff --git a/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php new file mode 100644 index 0000000..18b082e --- /dev/null +++ b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.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 Symfony\Component\Finder\Iterator; + +/** + * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). + * + * @author Fabien Potencier + */ +abstract class MultiplePcreFilterIterator extends \FilterIterator +{ + protected $matchRegexps = []; + protected $noMatchRegexps = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param array $matchPatterns An array of patterns that need to match + * @param array $noMatchPatterns An array of patterns that need to not match + */ + public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) + { + foreach ($matchPatterns as $pattern) { + $this->matchRegexps[] = $this->toRegex($pattern); + } + + foreach ($noMatchPatterns as $pattern) { + $this->noMatchRegexps[] = $this->toRegex($pattern); + } + + parent::__construct($iterator); + } + + /** + * Checks whether the string is accepted by the regex filters. + * + * If there is no regexps defined in the class, this method will accept the string. + * Such case can be handled by child classes before calling the method if they want to + * apply a different behavior. + * + * @param string $string The string to be matched against filters + * + * @return bool + */ + protected function isAccepted($string) + { + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $string)) { + return false; + } + } + + // should at least match one rule + if ($this->matchRegexps) { + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $string)) { + return true; + } + } + + return false; + } + + // If there is no match rules, the file is accepted + return true; + } + + /** + * Checks whether the string is a regex. + * + * @param string $str + * + * @return bool Whether the given string is a regex + */ + protected function isRegex($str) + { + if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) { + $start = substr($m[1], 0, 1); + $end = substr($m[1], -1); + + if ($start === $end) { + return !preg_match('/[*?[:alnum:] \\\\]/', $start); + } + + foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) { + if ($start === $delimiters[0] && $end === $delimiters[1]) { + return true; + } + } + } + + return false; + } + + /** + * Converts string into regexp. + * + * @param string $str Pattern + * + * @return string regexp corresponding to a given string + */ + abstract protected function toRegex($str); +} diff --git a/vendor/symfony/finder/Iterator/PathFilterIterator.php b/vendor/symfony/finder/Iterator/PathFilterIterator.php new file mode 100644 index 0000000..3fda557 --- /dev/null +++ b/vendor/symfony/finder/Iterator/PathFilterIterator.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 Symfony\Component\Finder\Iterator; + +/** + * PathFilterIterator filters files by path patterns (e.g. some/special/dir). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + */ +class PathFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $filename = $this->current()->getRelativePathname(); + + if ('\\' === \DIRECTORY_SEPARATOR) { + $filename = str_replace('\\', '/', $filename); + } + + return $this->isAccepted($filename); + } + + /** + * Converts strings to regexp. + * + * PCRE patterns are left unchanged. + * + * Default conversion: + * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' + * + * Use only / as directory separator (on Windows also). + * + * @param string $str Pattern: regexp or dirname + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php new file mode 100644 index 0000000..3facef5 --- /dev/null +++ b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Extends the \RecursiveDirectoryIterator to support relative paths. + * + * @author Victor Berchet + */ +class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator +{ + /** + * @var bool + */ + private $ignoreUnreadableDirs; + + /** + * @var bool + */ + private $rewindable; + + // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations + private $rootPath; + private $subPath; + private $directorySeparator = '/'; + + /** + * @throws \RuntimeException + */ + public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false) + { + if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { + throw new \RuntimeException('This iterator only support returning current as fileinfo.'); + } + + parent::__construct($path, $flags); + $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; + $this->rootPath = $path; + if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { + $this->directorySeparator = \DIRECTORY_SEPARATOR; + } + } + + /** + * Return an instance of SplFileInfo with support for relative paths. + * + * @return SplFileInfo File information + */ + public function current() + { + // the logic here avoids redoing the same work in all iterations + + if (null === $subPathname = $this->subPath) { + $subPathname = $this->subPath = (string) $this->getSubPath(); + } + if ('' !== $subPathname) { + $subPathname .= $this->directorySeparator; + } + $subPathname .= $this->getFilename(); + + return new SplFileInfo($this->rootPath.$this->directorySeparator.$subPathname, $this->subPath, $subPathname); + } + + /** + * @return \RecursiveIterator + * + * @throws AccessDeniedException + */ + public function getChildren() + { + try { + $children = parent::getChildren(); + + if ($children instanceof self) { + // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore + $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; + + // performance optimization to avoid redoing the same work in all children + $children->rewindable = &$this->rewindable; + $children->rootPath = $this->rootPath; + } + + return $children; + } catch (\UnexpectedValueException $e) { + if ($this->ignoreUnreadableDirs) { + // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. + return new \RecursiveArrayIterator([]); + } else { + throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); + } + } + } + + /** + * Do nothing for non rewindable stream. + */ + public function rewind() + { + if (false === $this->isRewindable()) { + return; + } + + parent::rewind(); + } + + /** + * Checks if the stream is rewindable. + * + * @return bool true when the stream is rewindable, false otherwise + */ + public function isRewindable() + { + if (null !== $this->rewindable) { + return $this->rewindable; + } + + if (false !== $stream = @opendir($this->getPath())) { + $infos = stream_get_meta_data($stream); + closedir($stream); + + if ($infos['seekable']) { + return $this->rewindable = true; + } + } + + return $this->rewindable = false; + } +} diff --git a/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php b/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php new file mode 100644 index 0000000..2aeef67 --- /dev/null +++ b/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.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 Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\NumberComparator; + +/** + * SizeRangeFilterIterator filters out files that are not in the given size range. + * + * @author Fabien Potencier + */ +class SizeRangeFilterIterator extends \FilterIterator +{ + private $comparators = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param NumberComparator[] $comparators An array of NumberComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + if (!$fileinfo->isFile()) { + return true; + } + + $filesize = $fileinfo->getSize(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filesize)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/SortableIterator.php b/vendor/symfony/finder/Iterator/SortableIterator.php new file mode 100644 index 0000000..8f0090c --- /dev/null +++ b/vendor/symfony/finder/Iterator/SortableIterator.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 Symfony\Component\Finder\Iterator; + +/** + * SortableIterator applies a sort on a given Iterator. + * + * @author Fabien Potencier + */ +class SortableIterator implements \IteratorAggregate +{ + const SORT_BY_NONE = 0; + const SORT_BY_NAME = 1; + const SORT_BY_TYPE = 2; + const SORT_BY_ACCESSED_TIME = 3; + const SORT_BY_CHANGED_TIME = 4; + const SORT_BY_MODIFIED_TIME = 5; + const SORT_BY_NAME_NATURAL = 6; + + private $iterator; + private $sort; + + /** + * @param \Traversable $iterator The Iterator to filter + * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) + * + * @throws \InvalidArgumentException + */ + public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false) + { + $this->iterator = $iterator; + $order = $reverseOrder ? -1 : 1; + + if (self::SORT_BY_NAME === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_NAME_NATURAL === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_TYPE === $sort) { + $this->sort = static function ($a, $b) use ($order) { + if ($a->isDir() && $b->isFile()) { + return -$order; + } elseif ($a->isFile() && $b->isDir()) { + return $order; + } + + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * ($a->getATime() - $b->getATime()); + }; + } elseif (self::SORT_BY_CHANGED_TIME === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * ($a->getCTime() - $b->getCTime()); + }; + } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * ($a->getMTime() - $b->getMTime()); + }; + } elseif (self::SORT_BY_NONE === $sort) { + $this->sort = $order; + } elseif (\is_callable($sort)) { + $this->sort = $reverseOrder ? static function ($a, $b) use ($sort) { return -$sort($a, $b); } : $sort; + } else { + throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); + } + } + + /** + * @return \Traversable + */ + public function getIterator() + { + if (1 === $this->sort) { + return $this->iterator; + } + + $array = iterator_to_array($this->iterator, true); + + if (-1 === $this->sort) { + $array = array_reverse($array); + } else { + uasort($array, $this->sort); + } + + return new \ArrayIterator($array); + } +} diff --git a/vendor/symfony/finder/LICENSE b/vendor/symfony/finder/LICENSE new file mode 100644 index 0000000..a677f43 --- /dev/null +++ b/vendor/symfony/finder/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/finder/README.md b/vendor/symfony/finder/README.md new file mode 100644 index 0000000..0b19c75 --- /dev/null +++ b/vendor/symfony/finder/README.md @@ -0,0 +1,14 @@ +Finder Component +================ + +The Finder component finds files and directories via an intuitive fluent +interface. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/finder.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/finder/SplFileInfo.php b/vendor/symfony/finder/SplFileInfo.php new file mode 100644 index 0000000..65d7423 --- /dev/null +++ b/vendor/symfony/finder/SplFileInfo.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Extends \SplFileInfo to support relative paths. + * + * @author Fabien Potencier + */ +class SplFileInfo extends \SplFileInfo +{ + private $relativePath; + private $relativePathname; + + /** + * @param string $file The file name + * @param string $relativePath The relative path + * @param string $relativePathname The relative path name + */ + public function __construct(string $file, string $relativePath, string $relativePathname) + { + parent::__construct($file); + $this->relativePath = $relativePath; + $this->relativePathname = $relativePathname; + } + + /** + * Returns the relative path. + * + * This path does not contain the file name. + * + * @return string the relative path + */ + public function getRelativePath() + { + return $this->relativePath; + } + + /** + * Returns the relative path name. + * + * This path contains the file name. + * + * @return string the relative path name + */ + public function getRelativePathname() + { + return $this->relativePathname; + } + + public function getFilenameWithoutExtension(): string + { + $filename = $this->getFilename(); + + return pathinfo($filename, PATHINFO_FILENAME); + } + + /** + * Returns the contents of the file. + * + * @return string the contents of the file + * + * @throws \RuntimeException + */ + public function getContents() + { + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $content = file_get_contents($this->getPathname()); + restore_error_handler(); + if (false === $content) { + throw new \RuntimeException($error); + } + + return $content; + } +} diff --git a/vendor/symfony/finder/composer.json b/vendor/symfony/finder/composer.json new file mode 100644 index 0000000..0b1408c --- /dev/null +++ b/vendor/symfony/finder/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/finder", + "type": "library", + "description": "Symfony Finder Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.1.3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Finder\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + } +} diff --git a/vendor/symfony/polyfill-ctype/Ctype.php b/vendor/symfony/polyfill-ctype/Ctype.php new file mode 100644 index 0000000..58414dc --- /dev/null +++ b/vendor/symfony/polyfill-ctype/Ctype.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Ctype; + +/** + * Ctype implementation through regex. + * + * @internal + * + * @author Gert de Pagter + */ +final class Ctype +{ + /** + * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. + * + * @see https://php.net/ctype-alnum + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is a letter, FALSE otherwise. + * + * @see https://php.net/ctype-alpha + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); + } + + /** + * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. + * + * @see https://php.net/ctype-cntrl + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); + } + + /** + * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. + * + * @see https://php.net/ctype-digit + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. + * + * @see https://php.net/ctype-graph + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); + } + + /** + * Returns TRUE if every character in text is a lowercase letter. + * + * @see https://php.net/ctype-lower + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); + } + + /** + * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. + * + * @see https://php.net/ctype-print + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); + } + + /** + * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. + * + * @see https://php.net/ctype-punct + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); + } + + /** + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. + * + * @see https://php.net/ctype-space + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); + } + + /** + * Returns TRUE if every character in text is an uppercase letter. + * + * @see https://php.net/ctype-upper + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); + } + + /** + * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + * + * @see https://php.net/ctype-xdigit + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); + } + + /** + * Converts integers to their char versions according to normal ctype behaviour, if needed. + * + * If an integer between -128 and 255 inclusive is provided, + * it is interpreted as the ASCII value of a single character + * (negative values have 256 added in order to allow characters in the Extended ASCII range). + * Any other integer is interpreted as a string containing the decimal digits of the integer. + * + * @param string|int $int + * + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int) + { + if (!\is_int($int)) { + return $int; + } + + if ($int < -128 || $int > 255) { + return (string) $int; + } + + if ($int < 0) { + $int += 256; + } + + return \chr($int); + } +} diff --git a/vendor/symfony/polyfill-ctype/LICENSE b/vendor/symfony/polyfill-ctype/LICENSE new file mode 100644 index 0000000..3f853aa --- /dev/null +++ b/vendor/symfony/polyfill-ctype/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-ctype/README.md b/vendor/symfony/polyfill-ctype/README.md new file mode 100644 index 0000000..8add1ab --- /dev/null +++ b/vendor/symfony/polyfill-ctype/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Ctype +======================== + +This component provides `ctype_*` functions to users who run php versions without the ctype extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-ctype/bootstrap.php b/vendor/symfony/polyfill-ctype/bootstrap.php new file mode 100644 index 0000000..14d1d0f --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (!function_exists('ctype_alnum')) { + function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } + function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } + function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } + function ctype_digit($text) { return p\Ctype::ctype_digit($text); } + function ctype_graph($text) { return p\Ctype::ctype_graph($text); } + function ctype_lower($text) { return p\Ctype::ctype_lower($text); } + function ctype_print($text) { return p\Ctype::ctype_print($text); } + function ctype_punct($text) { return p\Ctype::ctype_punct($text); } + function ctype_space($text) { return p\Ctype::ctype_space($text); } + function ctype_upper($text) { return p\Ctype::ctype_upper($text); } + function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } +} diff --git a/vendor/symfony/polyfill-ctype/composer.json b/vendor/symfony/polyfill-ctype/composer.json new file mode 100644 index 0000000..2a2ea04 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/polyfill-ctype", + "type": "library", + "description": "Symfony polyfill for ctype functions", + "keywords": ["polyfill", "compatibility", "portable", "ctype"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + } +} diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..15503bc --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,847 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + const MB_CASE_FOLD = PHP_INT_MAX; + + private static $encodingList = array('ASCII', 'UTF-8'); + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + private static $caseFold = array( + array('µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"), + array('μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'), + ); + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) + { + $vars = array(&$a, &$b, &$c, &$d, &$e, &$f); + + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || !$convmap) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return Mbstring::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || !$convmap) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !\is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, array(__CLASS__, 'title_case'), $s); + } else { + if (MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) { + self::$internalEncoding = $encoding; + + return true; + } + + return false; + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($lang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $lang; + + return true; + } + + return false; + } + + public static function mb_list_encodings() + { + return array('UTF-8'); + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return array('utf8'); + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING); + + return false; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && \method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + trigger_error('The length of each segment must be greater than zero', E_USER_WARNING); + + return false; + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + } + + $result = array(); + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (0 === strcasecmp($c, 'none')) { + return true; + } + + return null !== $c ? false : 'none'; + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrchr($haystack, $needle, $part); + } + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = array( + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ); + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000..342e828 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](http://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..e6fbfa6 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1096 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', +); diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000..2a8f6e7 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ᾈ', + 'ᾁ' => 'ᾉ', + 'ᾂ' => 'ᾊ', + 'ᾃ' => 'ᾋ', + 'ᾄ' => 'ᾌ', + 'ᾅ' => 'ᾍ', + 'ᾆ' => 'ᾎ', + 'ᾇ' => 'ᾏ', + 'ᾐ' => 'ᾘ', + 'ᾑ' => 'ᾙ', + 'ᾒ' => 'ᾚ', + 'ᾓ' => 'ᾛ', + 'ᾔ' => 'ᾜ', + 'ᾕ' => 'ᾝ', + 'ᾖ' => 'ᾞ', + 'ᾗ' => 'ᾟ', + 'ᾠ' => 'ᾨ', + 'ᾡ' => 'ᾩ', + 'ᾢ' => 'ᾪ', + 'ᾣ' => 'ᾫ', + 'ᾤ' => 'ᾬ', + 'ᾥ' => 'ᾭ', + 'ᾦ' => 'ᾮ', + 'ᾧ' => 'ᾯ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ᾼ', + 'ι' => 'Ι', + 'ῃ' => 'ῌ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ῼ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', +); diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..204a41b --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_strlen')) { + define('MB_CASE_UPPER', 0); + define('MB_CASE_LOWER', 1); + define('MB_CASE_TITLE', 2); + + function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); } + function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); } + function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); } + function mb_decode_numericentity($s, $convmap, $enc = null) { return p\Mbstring::mb_decode_numericentity($s, $convmap, $enc); } + function mb_encode_numericentity($s, $convmap, $enc = null, $is_hex = false) { return p\Mbstring::mb_encode_numericentity($s, $convmap, $enc, $is_hex); } + function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); } + function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); } + function mb_language($lang = null) { return p\Mbstring::mb_language($lang); } + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } + function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); } + function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); } + function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); } + function mb_parse_str($s, &$result = array()) { parse_str($s, $result); } + function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); } + function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); } + function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); } + function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); } + function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); } + function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); } + function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); } + function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); } + function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); } + function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); } + function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); } + function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); } + function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); } + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } + function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); } + function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); } + function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); } + function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); } + function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); } + function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); } +} +if (!function_exists('mb_chr')) { + function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); } + function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); } + function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); } +} + +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $split_length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $split_length, $encoding); } +} diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000..1a8bec5 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.14-dev" + } + } +} diff --git a/vendor/topthink/framework/.gitignore b/vendor/topthink/framework/.gitignore new file mode 100644 index 0000000..b267fba --- /dev/null +++ b/vendor/topthink/framework/.gitignore @@ -0,0 +1,7 @@ +/vendor +composer.phar +composer.lock +.DS_Store +Thumbs.db +/.idea +/.vscode \ No newline at end of file diff --git a/vendor/topthink/framework/.travis.yml b/vendor/topthink/framework/.travis.yml new file mode 100644 index 0000000..73e6681 --- /dev/null +++ b/vendor/topthink/framework/.travis.yml @@ -0,0 +1,34 @@ +dist: xenial +language: php + +matrix: + fast_finish: true + include: + - php: 7.1 + - php: 7.2 + - php: 7.3 + +cache: + directories: + - $HOME/.composer/cache + +services: + - memcached + - redis-server + - mysql + +before_install: + - echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - printf "\n" | pecl install -f redis + - travis_retry composer self-update + - mysql -e 'CREATE DATABASE test;' + +install: + - travis_retry composer update --prefer-dist --no-interaction --prefer-stable --no-suggest + +script: + - vendor/bin/phpunit --coverage-clover build/logs/coverage.xml + +after_script: + - travis_retry wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover build/logs/coverage.xml \ No newline at end of file diff --git a/vendor/topthink/framework/CONTRIBUTING.md b/vendor/topthink/framework/CONTRIBUTING.md new file mode 100644 index 0000000..efa3ad9 --- /dev/null +++ b/vendor/topthink/framework/CONTRIBUTING.md @@ -0,0 +1,119 @@ +如何贡献我的源代码 +=== + +此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。 + +## 通过 Github 贡献代码 + +ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。 + +参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请并。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。 + +我们希望你贡献的代码符合: + +* ThinkPHP 的编码规范 +* 适当的注释,能让其他人读懂 +* 遵循 Apache2 开源协议 + +**如果想要了解更多细节或有任何疑问,请继续阅读下面的内容** + +### 注意事项 + +* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141); +* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144); +* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。 +* 系统会自动在 PHP 7.1 ~ 7.3 上测试修改,请确保你的修改符合 PHP 7.1 ~ 7.3 的语法规范; +* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests); + +## GitHub Issue + +GitHub 提供了 Issue 功能,该功能可以用于: + +* 提出 bug +* 提出功能改进 +* 反馈使用体验 + +该功能不应该用于: + + * 提出修改意见(涉及代码署名和修订追溯问题) + * 不友善的言论 + +## 快速修改 + +**GitHub 提供了快速编辑文件的功能** + +1. 登录 GitHub 帐号; +2. 浏览项目文件,找到要进行修改的文件; +3. 点击右上角铅笔图标进行修改; +4. 填写 `Commit changes` 相关内容(Title 必填); +5. 提交修改,等待 CI 验证和管理员合并。 + +**若您需要一次提交大量修改,请继续阅读下面的内容** + +## 完整流程 + +1. `fork`本项目; +2. 克隆(`clone`)你 `fork` 的项目到本地; +3. 新建分支(`branch`)并检出(`checkout`)新分支; +4. 添加本项目到你的本地 git 仓库作为上游(`upstream`); +5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests); +6. 变基(衍合 `rebase`)你的分支到上游 master 分支; +7. `push` 你的本地仓库到 GitHub; +8. 提交 `pull request`; +9. 等待 CI 验证(若不通过则重复 5~7,GitHub 会自动更新你的 `pull request`); +10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。 + +*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`* + +*绝对不可以使用 `git push -f` 强行推送修改到上游* + +### 注意事项 + +* 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/); +* 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分); +* 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/) + +## 推荐资源 + +### 开发环境 + +* XAMPP for Windows 5.5.x +* WampServer (for Windows) +* upupw Apache PHP5.4 ( for Windows) + +或自行安装 + +- Apache / Nginx +- PHP 7.1 ~ 7.3 +- MySQL / MariaDB + +*Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer* + +*Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB* + +### 编辑器 + +Sublime Text 3 + phpfmt 插件 + +phpfmt 插件参数 + +```json +{ + "autocomplete": true, + "enable_auto_align": true, + "format_on_save": true, + "indent_with_space": true, + "psr1_naming": false, + "psr2": true, + "version": 4 +} +``` + +或其他 编辑器 / IDE 配合 PSR2 自动格式化工具 + +### Git GUI + +* SourceTree +* GitHub Desktop + +或其他 Git 图形界面客户端 diff --git a/vendor/topthink/framework/LICENSE.txt b/vendor/topthink/framework/LICENSE.txt new file mode 100644 index 0000000..4e910bb --- /dev/null +++ b/vendor/topthink/framework/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2019 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/topthink/framework/README.md b/vendor/topthink/framework/README.md new file mode 100644 index 0000000..8c856b4 --- /dev/null +++ b/vendor/topthink/framework/README.md @@ -0,0 +1,86 @@ +![](https://box.kancloud.cn/5a0aaa69a5ff42657b5c4715f3d49221) + +ThinkPHP 6.0 +=============== + +[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=6.0)](https://travis-ci.org/top-think/framework) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/top-think/framework/badges/quality-score.png?b=6.0)](https://scrutinizer-ci.com/g/top-think/framework/?branch=6.0) +[![Code Coverage](https://scrutinizer-ci.com/g/top-think/framework/badges/coverage.png?b=6.0)](https://scrutinizer-ci.com/g/top-think/framework/?branch=6.0) +[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework) +[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework) +[![PHP Version](https://img.shields.io/badge/php-%3E%3D7.1-8892BF.svg)](http://www.php.net/) +[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) + +ThinkPHP6.0底层架构采用PHP7.1改写和进一步优化。 + +## 主要新特性 + +* 采用`PHP7`强类型(严格模式) +* 支持更多的`PSR`规范 +* 原生多应用支持 +* 系统服务注入支持 +* ORM作为独立组件使用 +* 增加Filesystem +* 全新的事件系统 +* 模板引擎分离出核心 +* 内部功能中间件化 +* SESSION机制改进 +* 日志多通道支持 +* 规范扩展接口 +* 更强大的控制台 +* 对Swoole以及协程支持改进 +* 对IDE更加友好 +* 统一和精简大量用法 + + +> ThinkPHP6.0的运行环境要求PHP7.1+。 + +## 安装 + +~~~ +composer create-project topthink/think tp +~~~ + +启动服务 + +~~~ +cd tp +php think run +~~~ + +然后就可以在浏览器中访问 + +~~~ +http://localhost:8000 +~~~ + +如果需要更新框架使用 +~~~ +composer update topthink/framework +~~~ + +## 文档 + +[完全开发手册](https://www.kancloud.cn/manual/thinkphp6_0/content) + +## 命名规范 + +`ThinkPHP6`遵循PSR-2命名规范和PSR-4自动加载规范。 + +## 参与开发 + +请参阅 [ThinkPHP核心框架包](https://github.com/top-think/framework)。 + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2019 by ThinkPHP (http://thinkphp.cn) + +All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/vendor/topthink/framework/composer.json b/vendor/topthink/framework/composer.json new file mode 100644 index 0000000..017b3d9 --- /dev/null +++ b/vendor/topthink/framework/composer.json @@ -0,0 +1,55 @@ +{ + "name": "topthink/framework", + "description": "The ThinkPHP Framework.", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "php": ">=7.1.0", + "ext-json": "*", + "ext-mbstring": "*", + "league/flysystem": "^1.0", + "league/flysystem-cached-adapter": "^1.0", + "opis/closure": "^3.1", + "psr/log": "~1.0", + "psr/container": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-orm": "^2.0", + "topthink/think-helper": "^3.1.1" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^7.0" + }, + "autoload": { + "files": [], + "psr-4": { + "think\\": "src/think/" + } + }, + "autoload-dev": { + "psr-4": { + "think\\tests\\": "src/tests/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "sort-packages": true + } +} diff --git a/vendor/topthink/framework/logo.png b/vendor/topthink/framework/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..25fd0593688de5c9f4cd321da1a72ab9566fe331 GIT binary patch literal 6995 zcmV-Z8?5AsP)KLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde03c&XQcVB=dL;k=fP(-4`Tqa_faw4Lbua(`>RI+y?e7jKeZ#YO-C z5P(TUK~#9!?3{UYROQ`(pS#SOWU`Z$BxDa^-w_0qRROJ_;)fy#YSH7?R(p-EbfAWiV7kMvdB&nAVk8FL`WvH-R=7$piE{anUI+fzQgaoxpU{e z?>zT?fBU_{y(@BL2)I=z*^UwhrBB4Gy1NZP^@0FsfM9?m zni!jV17^vBVHn-U3SSTeCDDXMvXbQ}q9l1ZKFxCxV26x|C7C!&G6175J~lZPfZnN>kQrBS-YxP4wF1jhNB;QPBv~1dJ^|$-!0_NXtSR(MALn;`VETA$ z^7(aXE(m~L%}u|waU@wY{ElZiiph2qqiV`UfT35Pj!ll`@?JLub*@WOMxYuO0frQh z+RW&jnPkNk1^vD_c_=2)f`M@nU~5q{FPU)#Tv2#?$aAtCB_vpTpzGR2fUOOOAc)NB z^B^(i_>kw>O%Bpx^U%)IHtv=H4Gg@Ri|HkIQkF8Z-SabJ3(?P0nyXs^^e9fo42dL=^itc4<@j~YGe*{@Hcl=KXB7)F%YR0E| zC`jth!1M`tHTVAyfIiKQMYeNu|3|s1%ujjLW)#gE8lurskdj3+!)iSOO%F;6rfMmMxJ)Pzb#T)~f@M`T}3q>QoLm63&4b&(U_o1c~4M|tX~ zh>d;d)Q)z~DNg>WTctd86lt-&sB_hH$l9Nm6=-1KQJaxPGgFK2;8&Nt6j69y%}v!0 z+d>*2-Oz})rc#ErqI!0Q+o=WM*922j;~sJcRa;sCBFyp_IbW21JH)>SV@50Q~J z(6LG}T$VRG;JcpjWu(RCanxCLPOei_0BX95Pxp`!o6m&&xs1rZs?$2Az16qt_&Usz zErfgH;?kUJ$#w+xhnhq)g-L@r+_>lb1Jn%-ujVGnmciKES&YfO9=pjARo$xUKHlE@ zESjMqVG8oSLZUT|D~lF}9HS_C2%jBV(&8wd<2LRTKm!A>>LSJz&zRin8J~YMiPo;^ zko#c&3sf|0`LWE|dS0sT;B{v;e$%hp$VwHl&%xZ&pqf0*>z$)uWf(ibo?9Wg}GHHy;Cn?X7Bsk|MQ}ml$dO48uuZKQPzPi+qIcQ zTLx1KZ)J4PnMk7CrSP^L{e+jdJ%plrgDQTH+Dwk4jQIl}#}dM@w3ZZmPjt?`o+5|4 z>U2Y8c-C~TF1?2&TSk}1&z~N6oj3RV72VK7!pnA)uyE)zI4mh)kLxfeb*js&U4UNI zg~OV{jIv)aJZpNFgLA7+R_uC;b=Au;NtU2)ky~++o6wtuL;i;(TV{vGx0@V@f*2iu zZq-R);y~u~e}wedUR@4vf5T>$?tFqnr*>kMV*(-u0|U3>q)(60%p34W9H%?CIwF!F z1&u^>Lu<24&@Mo?;$%?fB8K5GA{21uIv3k z$WSgEA2txGOp+~~I@kB@LX<=OfgxE_m^a{$m^$I5pNo83yPEg+KSxdDUMx}!<{)6a znj5QR=eoWRFeZ0ar>@tmoI+~_(W|*APaVy7$3LgueFRC6!w2ZqT$CgaV{ZRPyA)pa zsokusc!6z4KS4mmCdUO?Ejk{xnf%25%i2NS_%hPc=&j?U%9mJQy#lxD#3I{+>Lj1$Mi$LnQFI$u7T(_B z!c!=zZK?>^rUC*kAe&t502OzPI*pH>#Pc}>;^hkIfhts0z)&eV9kM7 zLasvj>`Y3EJ)28<{w(V7wjW!|2m7Dr;K}chMGH1lY|!oM)m$W5)0t2(kFmM4BA(gV zQ@@c$&j~E8Zk&MlV@y9PX9nvJufQ<$pplRK)4umok}@o+S(I9574PqRj&EyM17Ho2 z2=J;dta3{pZ&JZ6QFGkCu6Zv2ieVU-B^#NJmUg<#xI#)(MzHPZCqdPQ{1bJ^OXw4| z?fP-mt9*Rm6`a14ZR$BukQkUgo+*X5k(*E+wVS!O{%iKue;wfdXofVc2GpS$!eR_KC1jDislKb z=hV|(2|123Cgk7Du&inAb{Iq?u0Hr0d+WXqxS0km+jm&@G{56EIhL~2k*qzqqz!-u zGV7VDZ-9py*yqq9tHknB_n{lw=@RF{poO03!mHxBP51DFyB4!#r*&(BS8Zl|{+y7}O^i8# zp7CXDJN7A(6a=6wnmOFKo0`VG^mLSVc!KZm|2yyPdk$GNVUjF?EZO|xN=;YL43$fBuI1{Idx*1i{aaMNs(F{CyZ($@aR!B_87d~p z%b-zSr3<=fhim{PcaSaV`n6I`+TX+Erc5t}K_Z|nOs4>6{A zt}Va+yd+{>N+WOYd6qA#mmvxAX`^8TglwwjV|KsaQWQprm~79+Zi>UFd4F6eer~uV z$~3r-@Xe8xVNG&X#UfT$F2*MN!}F>x(qnsZ_wc`;7kE35uj8o=x3KomzcERcptYMb zs^!X}+qpF7+OWorx1?Y*IWW#je+7&zi6*{0{3fGwW(HWQvt`qfmAtd}ZZ?H0GdP#HH~}OaqM#jW=sqj)4s9uaYRA#bH06=n~b;u77lra1Z$5j1E(7r>kq%l zs)|L_xoWUV_K=AO-O%xAE+!W)U`E+5I&>XT5V4!${Ccc|rdkCAzNlUn6rg6Pys_g? zG`xTQ4ZaK#*3T{jYq`|GM+B@v7+Ym5OMe{Jd_zrU8%ew^Jk){e(R{Vo4wX4xj-@LZO ztGTh89Nf_7zZsf&MJKKt20#G;RaaPh;NN}#J!&(nD_;(%%c{CUdTcMogNsgXR-kXI2bDXH4EK zW|aN1&FvkIcr`aoz6QjzoR^RQTO8|V)>OU9ya9gzV9U|f2(7w;4Gd(_OmXs%sKGmM zayxJDd6qqO8<9m5wsy*_#_8fykJvn}DZP(DA&g;1rQY>DPHzLED7O#r%qrY+bPbc6 z=96O2;_#_G$dbABY_Z68lI@Xy0g_vF@?pgyK0WX{KHbxEi<7SoFFBtC-4 ziUTLN<5rveR(_iR1wlN$3SZ{R;)OITr~H;LfR2tuvPHa#x37K^OLzX6LyfzzO7?cs zU1z$+3X=vgt@zHUbdBve{sls-oN2qgF&4?rmZNJ(kITm_Spy!E5)koe9@GeQyr0>A zD=QYUs$vnU?!zv}wUfA2@u_Zl#O5+Fe;%W9X0%xdKUb-BRfxm4Sf`6WCq2hmvf~eIsa=Cbwg*jmp6w8OH5U#`G86LWa(S;C zi8DtpS@GGyCAgKQ05uZUtr7(Zx6*hzfEEH=9z}OkeQFo^i384gf`?A+WbxMDaHOfC zO(XGB)y~eCoa>lSFqgELyr{ZP)s4IPs^;CGJ%?eh^|tCIq9C#B#0JI%d7K~|*?w#- zuWtJ-&C01ZT@9b+Mi3+hWlUsJ!ThLI5nJolq1T z@6c~Ie*Yf-+Ws(xp@%d?ita<#Rf>`aGo|o0dZ%8}WufA`d;i9s`i-G(Y-DsV9a+V=ytZTF{SBLW?YrNf{+<66G+jl}z4T2R%QdCAJy|3W|cn}vBt^p-4q|69C(dY6^rnw&bHjBoxo$j zlGbia9T9w!ulcNG?AcF=H!G+3uwqd_Z;7g_Oe~nk%(DBteAONJVLQurKgIrr&7tCX lAFv5{16U3OylCP71_0o>R4vh7BrN~{002ovPDHLkV1liRoDKj0 literal 0 HcmV?d00001 diff --git a/vendor/topthink/framework/phpunit.xml.dist b/vendor/topthink/framework/phpunit.xml.dist new file mode 100644 index 0000000..e20a133 --- /dev/null +++ b/vendor/topthink/framework/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + ./tests + + + + + ./src/think + + + diff --git a/vendor/topthink/framework/src/helper.php b/vendor/topthink/framework/src/helper.php new file mode 100644 index 0000000..d1ba047 --- /dev/null +++ b/vendor/topthink/framework/src/helper.php @@ -0,0 +1,663 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +//------------------------ +// ThinkPHP 助手函数 +//------------------------- + +use think\App; +use think\Container; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\facade\Cache; +use think\facade\Config; +use think\facade\Cookie; +use think\facade\Env; +use think\facade\Event; +use think\facade\Lang; +use think\facade\Log; +use think\facade\Request; +use think\facade\Route; +use think\facade\Session; +use think\Response; +use think\response\File; +use think\response\Json; +use think\response\Jsonp; +use think\response\Redirect; +use think\response\View; +use think\response\Xml; +use think\route\Url as UrlBuild; +use think\Validate; + +if (!function_exists('abort')) { + /** + * 抛出HTTP异常 + * @param integer|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 + */ + function abort($code, string $message = '', array $header = []) + { + if ($code instanceof Response) { + throw new HttpResponseException($code); + } else { + throw new HttpException($code, $message, null, $header); + } + } +} + +if (!function_exists('app')) { + /** + * 快速获取容器中的实例 支持依赖注入 + * @param string $name 类名或标识 默认获取当前应用实例 + * @param array $args 参数 + * @param bool $newInstance 是否每次创建新的实例 + * @return object|App + */ + function app(string $name = '', array $args = [], bool $newInstance = false) + { + return Container::getInstance()->make($name ?: App::class, $args, $newInstance); + } +} + +if (!function_exists('bind')) { + /** + * 绑定一个类到容器 + * @param string|array $abstract 类标识、接口(支持批量绑定) + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return Container + */ + function bind($abstract, $concrete = null) + { + return Container::getInstance()->bind($abstract, $concrete); + } +} + +if (!function_exists('cache')) { + /** + * 缓存管理 + * @param string $name 缓存名称 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @param string $tag 缓存标签 + * @return mixed + */ + function cache(string $name = null, $value = '', $options = null, $tag = null) + { + if (is_null($name)) { + return app('cache'); + } + + if ('' === $value) { + // 获取缓存 + return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name); + } elseif (is_null($value)) { + // 删除缓存 + return Cache::delete($name); + } + + // 缓存数据 + if (is_array($options)) { + $expire = $options['expire'] ?? null; //修复查询缓存无法设置过期时间 + } else { + $expire = $options; + } + + if (is_null($tag)) { + return Cache::set($name, $value, $expire); + } else { + return Cache::tag($tag)->set($name, $value, $expire); + } + } +} + +if (!function_exists('config')) { + /** + * 获取和设置配置参数 + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + function config($name = '', $value = null) + { + if (is_array($name)) { + return Config::set($name, $value); + } + + return 0 === strpos($name, '?') ? Config::has(substr($name, 1)) : Config::get($name, $value); + } +} + +if (!function_exists('cookie')) { + /** + * Cookie管理 + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $option 参数 + * @return mixed + */ + function cookie(string $name, $value = '', $option = null) + { + if (is_null($value)) { + // 删除 + Cookie::delete($name); + } elseif ('' === $value) { + // 获取 + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1)) : Cookie::get($name); + } else { + // 设置 + return Cookie::set($name, $value, $option); + } + } +} + +if (!function_exists('download')) { + /** + * 获取\think\response\Download对象实例 + * @param string $filename 要下载的文件 + * @param string $name 显示文件名 + * @param bool $content 是否为内容 + * @param int $expire 有效期(秒) + * @return \think\response\File + */ + function download(string $filename, string $name = '', bool $content = false, int $expire = 180): File + { + return Response::create($filename, 'file')->name($name)->isContent($content)->expire($expire); + } +} + +if (!function_exists('dump')) { + /** + * 浏览器友好的变量输出 + * @param mixed $vars 要输出的变量 + * @return void + */ + function dump(...$vars) + { + ob_start(); + var_dump(...$vars); + + $output = ob_get_clean(); + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); + + if (PHP_SAPI == 'cli') { + $output = PHP_EOL . $output . PHP_EOL; + } else { + if (!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, ENT_SUBSTITUTE); + } + $output = '
' . $output . '
'; + } + + echo $output; + } +} + +if (!function_exists('env')) { + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名(支持二级 .号分割) + * @param string $default 默认值 + * @return mixed + */ + function env(string $name = null, $default = null) + { + return Env::get($name, $default); + } +} + +if (!function_exists('event')) { + /** + * 触发事件 + * @param mixed $event 事件名(或者类名) + * @param mixed $args 参数 + * @return mixed + */ + function event($event, $args = null) + { + return Event::trigger($event, $args); + } +} + +if (!function_exists('halt')) { + /** + * 调试变量并且中断输出 + * @param mixed $vars 调试变量或者信息 + */ + function halt(...$vars) + { + dump(...$vars); + + throw new HttpResponseException(Response::create()); + } +} + +if (!function_exists('input')) { + /** + * 获取输入数据 支持默认值和过滤 + * @param string $key 获取的变量名 + * @param mixed $default 默认值 + * @param string $filter 过滤方法 + * @return mixed + */ + function input(string $key = '', $default = null, $filter = '') + { + if (0 === strpos($key, '?')) { + $key = substr($key, 1); + $has = true; + } + + if ($pos = strpos($key, '.')) { + // 指定参数来源 + $method = substr($key, 0, $pos); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = substr($key, $pos + 1); + if ('server' == $method && is_null($default)) { + $default = ''; + } + } else { + $method = 'param'; + } + } else { + // 默认为自动判断 + $method = 'param'; + } + + return isset($has) ? + request()->has($key, $method) : + request()->$method($key, $default, $filter); + } +} + +if (!function_exists('invoke')) { + /** + * 调用反射实例化对象或者执行方法 支持依赖注入 + * @param mixed $call 类名或者callable + * @param array $args 参数 + * @return mixed + */ + function invoke($call, array $args = []) + { + if (is_callable($call)) { + return Container::getInstance()->invoke($call, $args); + } + + return Container::getInstance()->invokeClass($call, $args); + } +} + +if (!function_exists('json')) { + /** + * 获取\think\response\Json对象实例 + * @param mixed $data 返回的数据 + * @param int $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Json + */ + function json($data = [], $code = 200, $header = [], $options = []): Json + { + return Response::create($data, 'json', $code)->header($header)->options($options); + } +} + +if (!function_exists('jsonp')) { + /** + * 获取\think\response\Jsonp对象实例 + * @param mixed $data 返回的数据 + * @param int $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Jsonp + */ + function jsonp($data = [], $code = 200, $header = [], $options = []): Jsonp + { + return Response::create($data, 'jsonp', $code)->header($header)->options($options); + } +} + +if (!function_exists('lang')) { + /** + * 获取语言变量值 + * @param string $name 语言变量名 + * @param array $vars 动态变量值 + * @param string $lang 语言 + * @return mixed + */ + function lang(string $name, array $vars = [], string $lang = '') + { + return Lang::get($name, $vars, $lang); + } +} + +if (!function_exists('parse_name')) { + /** + * 字符串命名风格转换 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 + * @param string $name 字符串 + * @param int $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + function parse_name(string $name, int $type = 0, bool $ucfirst = true): string + { + if ($type) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { + return strtoupper($match[1]); + }, $name); + + return $ucfirst ? ucfirst($name) : lcfirst($name); + } + + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } +} + +if (!function_exists('redirect')) { + /** + * 获取\think\response\Redirect对象实例 + * @param string $url 重定向地址 + * @param int $code 状态码 + * @return \think\response\Redirect + */ + function redirect(string $url = '', int $code = 302): Redirect + { + return Response::create($url, 'redirect', $code); + } +} + +if (!function_exists('request')) { + /** + * 获取当前Request对象实例 + * @return Request + */ + function request(): \think\Request + { + return app('request'); + } +} + +if (!function_exists('response')) { + /** + * 创建普通 Response 对象实例 + * @param mixed $data 输出数据 + * @param int|string $code 状态码 + * @param array $header 头信息 + * @param string $type + * @return Response + */ + function response($data = '', $code = 200, $header = [], $type = 'html'): Response + { + return Response::create($data, $type, $code)->header($header); + } +} + +if (!function_exists('session')) { + /** + * Session管理 + * @param string $name session名称 + * @param mixed $value session值 + * @return mixed + */ + function session($name = '', $value = '') + { + if (is_null($name)) { + // 清除 + Session::clear(); + } elseif ('' === $name) { + return Session::all(); + } elseif (is_null($value)) { + // 删除 + Session::delete($name); + } elseif ('' === $value) { + // 判断或获取 + return 0 === strpos($name, '?') ? Session::has(substr($name, 1)) : Session::get($name); + } else { + // 设置 + Session::set($name, $value); + } + } +} + +if (!function_exists('token')) { + /** + * 获取Token令牌 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token(string $name = '__token__', string $type = 'md5'): string + { + return Request::buildToken($name, $type); + } +} + +if (!function_exists('token_field')) { + /** + * 生成令牌隐藏表单 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token_field(string $name = '__token__', string $type = 'md5'): string + { + $token = Request::buildToken($name, $type); + + return ''; + } +} + +if (!function_exists('token_meta')) { + /** + * 生成令牌meta + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token_meta(string $name = '__token__', string $type = 'md5'): string + { + $token = Request::buildToken($name, $type); + + return ''; + } +} + +if (!function_exists('trace')) { + /** + * 记录日志信息 + * @param mixed $log log信息 支持字符串和数组 + * @param string $level 日志级别 + * @return array|void + */ + function trace($log = '[think]', string $level = 'log') + { + if ('[think]' === $log) { + return Log::getLog(); + } + + Log::record($log, $level); + } +} + +if (!function_exists('url')) { + /** + * Url生成 + * @param string $url 路由地址 + * @param array $vars 变量 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return UrlBuild + */ + function url(string $url = '', array $vars = [], $suffix = true, $domain = false): UrlBuild + { + return Route::buildUrl($url, $vars)->suffix($suffix)->domain($domain); + } +} + +if (!function_exists('validate')) { + /** + * 生成验证对象 + * @param string|array $validate 验证器类名或者验证规则数组 + * @param array $message 错误提示信息 + * @param bool $batch 是否批量验证 + * @param bool $failException 是否抛出异常 + * @return Validate + */ + function validate($validate = '', array $message = [], bool $batch = false, bool $failException = true): Validate + { + if (is_array($validate) || '' === $validate) { + $v = new Validate(); + if (is_array($validate)) { + $v->rule($validate); + } + } else { + if (strpos($validate, '.')) { + // 支持场景 + [$validate, $scene] = explode('.', $validate); + } + + $class = false !== strpos($validate, '\\') ? $validate : app()->parseClass('validate', $validate); + + $v = new $class(); + + if (!empty($scene)) { + $v->scene($scene); + } + } + + return $v->message($message)->batch($batch)->failException($failException); + } +} + +if (!function_exists('view')) { + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param int $code 状态码 + * @param callable $filter 内容过滤 + * @return \think\response\View + */ + function view(string $template = '', $vars = [], $code = 200, $filter = null): View + { + return Response::create($template, 'view', $code)->assign($vars)->filter($filter); + } +} + +if (!function_exists('display')) { + /** + * 渲染模板输出 + * @param string $content 渲染内容 + * @param array $vars 模板变量 + * @param int $code 状态码 + * @param callable $filter 内容过滤 + * @return \think\response\View + */ + function display(string $content, $vars = [], $code = 200, $filter = null): View + { + return Response::create($content, 'view', $code)->isContent(true)->assign($vars)->filter($filter); + } +} + +if (!function_exists('xml')) { + /** + * 获取\think\response\Xml对象实例 + * @param mixed $data 返回的数据 + * @param int $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Xml + */ + function xml($data = [], $code = 200, $header = [], $options = []): Xml + { + return Response::create($data, 'xml', $code)->header($header)->options($options); + } +} + +if (!function_exists('app_path')) { + /** + * 获取当前应用目录 + * + * @param string $path + * @return string + */ + function app_path($path = '') + { + return app()->getAppPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('base_path')) { + /** + * 获取应用基础目录 + * + * @param string $path + * @return string + */ + function base_path($path = '') + { + return app()->getBasePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('config_path')) { + /** + * 获取应用配置目录 + * + * @param string $path + * @return string + */ + function config_path($path = '') + { + return app()->getConfigPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('public_path')) { + /** + * 获取web根目录 + * + * @param string $path + * @return string + */ + function public_path($path = '') + { + return app()->getRootPath() . ($path ? ltrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('runtime_path')) { + /** + * 获取应用运行时目录 + * + * @param string $path + * @return string + */ + function runtime_path($path = '') + { + return app()->getRuntimePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('root_path')) { + /** + * 获取项目根目录 + * + * @param string $path + * @return string + */ + function root_path($path = '') + { + return app()->getRootPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} diff --git a/vendor/topthink/framework/src/lang/zh-cn.php b/vendor/topthink/framework/src/lang/zh-cn.php new file mode 100644 index 0000000..83d8a1e --- /dev/null +++ b/vendor/topthink/framework/src/lang/zh-cn.php @@ -0,0 +1,148 @@ + +// +---------------------------------------------------------------------- + +// 核心中文语言包 +return [ + // 系统错误提示 + 'Undefined variable' => '未定义变量', + 'Undefined index' => '未定义数组索引', + 'Undefined offset' => '未定义数组下标', + 'Parse error' => '语法解析错误', + 'Type error' => '类型错误', + 'Fatal error' => '致命错误', + 'syntax error' => '语法错误', + + // 框架核心错误提示 + 'dispatch type not support' => '不支持的调度类型', + 'method param miss' => '方法参数错误', + 'method not exists' => '方法不存在', + 'function not exists' => '函数不存在', + 'app not exists' => '应用不存在', + 'controller not exists' => '控制器不存在', + 'class not exists' => '类不存在', + 'property not exists' => '类的属性不存在', + 'template not exists' => '模板文件不存在', + 'illegal controller name' => '非法的控制器名称', + 'illegal action name' => '非法的操作名称', + 'url suffix deny' => '禁止的URL后缀访问', + 'Undefined cache config' => '缓存配置未定义', + 'Route Not Found' => '当前访问路由未定义或不匹配', + 'Undefined db config' => '数据库配置未定义', + 'Undefined log config' => '日志配置未定义', + 'Undefined db type' => '未定义数据库类型', + 'variable type error' => '变量类型错误', + 'PSR-4 error' => 'PSR-4 规范错误', + 'not support type' => '不支持的分页索引字段类型', + 'not support total' => '简洁模式下不能获取数据总数', + 'not support last' => '简洁模式下不能获取最后一页', + 'error session handler' => '错误的SESSION处理器类', + 'not allow php tag' => '模板不允许使用PHP语法', + 'not support' => '不支持', + 'database config error' => '数据库配置信息错误', + 'redisd master' => 'Redisd 主服务器错误', + 'redisd slave' => 'Redisd 从服务器错误', + 'must run at sae' => '必须在SAE运行', + 'memcache init error' => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务', + 'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务', + 'fields not exists' => '数据表字段不存在', + 'where express error' => '查询表达式错误', + 'no data to update' => '没有任何数据需要更新', + 'miss data to insert' => '缺少需要写入的数据', + 'miss complex primary data' => '缺少复合主键数据', + 'miss update condition' => '缺少更新条件', + 'model data Not Found' => '模型数据不存在', + 'table data not Found' => '表数据不存在', + 'delete without condition' => '没有条件不会执行删除操作', + 'miss relation data' => '缺少关联表数据', + 'tag attr must' => '模板标签属性必须', + 'tag error' => '模板标签错误', + 'cache write error' => '缓存写入失败', + 'sae mc write error' => 'SAE mc 写入错误', + 'route name not exists' => '路由标识不存在(或参数不够)', + 'invalid request' => '非法请求', + 'bind attr has exists' => '模型的属性已经存在', + 'relation data not exists' => '关联数据不存在', + 'relation not support' => '关联不支持', + 'chunk not support order' => 'Chunk不支持调用order方法', + 'route pattern error' => '路由变量规则定义错误', + 'route behavior will not support' => '路由行为废弃(使用中间件替代)', + 'closure not support cache(true)' => '使用闭包查询不支持cache(true),请指定缓存Key', + + // 上传错误信息 + 'unknown upload error' => '未知上传错误!', + 'file write error' => '文件写入失败!', + 'upload temp dir not found' => '找不到临时文件夹!', + 'no file to uploaded' => '没有文件被上传!', + 'only the portion of file is uploaded' => '文件只有部分被上传!', + 'upload File size exceeds the maximum value' => '上传文件大小超过了最大值!', + 'upload write error' => '文件上传保存错误!', + 'has the same filename: {:filename}' => '存在同名文件:{:filename}', + 'upload illegal files' => '非法上传文件', + 'illegal image files' => '非法图片文件', + 'extensions to upload is not allowed' => '上传文件后缀不允许', + 'mimetype to upload is not allowed' => '上传文件MIME类型不允许!', + 'filesize not match' => '上传文件大小不符!', + 'directory {:path} creation failed' => '目录 {:path} 创建失败!', + + 'The middleware must return Response instance' => '中间件方法必须返回Response对象实例', + 'The queue was exhausted, with no response returned' => '中间件队列为空', + // Validate Error Message + ':attribute require' => ':attribute不能为空', + ':attribute must' => ':attribute必须', + ':attribute must be numeric' => ':attribute必须是数字', + ':attribute must be integer' => ':attribute必须是整数', + ':attribute must be float' => ':attribute必须是浮点数', + ':attribute must be bool' => ':attribute必须是布尔值', + ':attribute not a valid email address' => ':attribute格式不符', + ':attribute not a valid mobile' => ':attribute格式不符', + ':attribute must be a array' => ':attribute必须是数组', + ':attribute must be yes,on or 1' => ':attribute必须是yes、on或者1', + ':attribute not a valid datetime' => ':attribute不是一个有效的日期或时间格式', + ':attribute not a valid file' => ':attribute不是有效的上传文件', + ':attribute not a valid image' => ':attribute不是有效的图像文件', + ':attribute must be alpha' => ':attribute只能是字母', + ':attribute must be alpha-numeric' => ':attribute只能是字母和数字', + ':attribute must be alpha-numeric, dash, underscore' => ':attribute只能是字母、数字和下划线_及破折号-', + ':attribute not a valid domain or ip' => ':attribute不是有效的域名或者IP', + ':attribute must be chinese' => ':attribute只能是汉字', + ':attribute must be chinese or alpha' => ':attribute只能是汉字、字母', + ':attribute must be chinese,alpha-numeric' => ':attribute只能是汉字、字母和数字', + ':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-', + ':attribute not a valid url' => ':attribute不是有效的URL地址', + ':attribute not a valid ip' => ':attribute不是有效的IP地址', + ':attribute must be dateFormat of :rule' => ':attribute必须使用日期格式 :rule', + ':attribute must be in :rule' => ':attribute必须在 :rule 范围内', + ':attribute be notin :rule' => ':attribute不能在 :rule 范围内', + ':attribute must between :1 - :2' => ':attribute只能在 :1 - :2 之间', + ':attribute not between :1 - :2' => ':attribute不能在 :1 - :2 之间', + 'size of :attribute must be :rule' => ':attribute长度不符合要求 :rule', + 'max size of :attribute must be :rule' => ':attribute长度不能超过 :rule', + 'min size of :attribute must be :rule' => ':attribute长度不能小于 :rule', + ':attribute cannot be less than :rule' => ':attribute日期不能小于 :rule', + ':attribute cannot exceed :rule' => ':attribute日期不能超过 :rule', + ':attribute not within :rule' => '不在有效期内 :rule', + 'access IP is not allowed' => '不允许的IP访问', + 'access IP denied' => '禁止的IP访问', + ':attribute out of accord with :2' => ':attribute和确认字段:2不一致', + ':attribute cannot be same with :2' => ':attribute和比较字段:2不能相同', + ':attribute must greater than or equal :rule' => ':attribute必须大于等于 :rule', + ':attribute must greater than :rule' => ':attribute必须大于 :rule', + ':attribute must less than or equal :rule' => ':attribute必须小于等于 :rule', + ':attribute must less than :rule' => ':attribute必须小于 :rule', + ':attribute must equal :rule' => ':attribute必须等于 :rule', + ':attribute has exists' => ':attribute已存在', + ':attribute not conform to the rules' => ':attribute不符合指定规则', + 'invalid Request method' => '无效的请求类型', + 'invalid token' => '令牌数据无效', + 'not conform to the rules' => '规则错误', + + 'record has update' => '记录已经被更新了', +]; diff --git a/vendor/topthink/framework/src/think/App.php b/vendor/topthink/framework/src/think/App.php new file mode 100644 index 0000000..2e9d71a --- /dev/null +++ b/vendor/topthink/framework/src/think/App.php @@ -0,0 +1,611 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\event\AppInit; +use think\helper\Str; +use think\initializer\BootService; +use think\initializer\Error; +use think\initializer\RegisterService; + +/** + * App 基础类 + * @property Route $route + * @property Config $config + * @property Cache $cache + * @property Request $request + * @property Http $http + * @property Console $console + * @property Env $env + * @property Event $event + * @property Middleware $middleware + * @property Log $log + * @property Lang $lang + * @property Db $db + * @property Cookie $cookie + * @property Session $session + * @property Validate $validate + * @property Filesystem $filesystem + */ +class App extends Container +{ + const VERSION = '6.0.0'; + + /** + * 应用调试模式 + * @var bool + */ + protected $appDebug = false; + + /** + * 应用开始时间 + * @var float + */ + protected $beginTime; + + /** + * 应用内存初始占用 + * @var integer + */ + protected $beginMem; + + /** + * 当前应用类库命名空间 + * @var string + */ + protected $namespace = 'app'; + + /** + * 应用根目录 + * @var string + */ + protected $rootPath = ''; + + /** + * 框架目录 + * @var string + */ + protected $thinkPath = ''; + + /** + * 应用目录 + * @var string + */ + protected $appPath = ''; + + /** + * Runtime目录 + * @var string + */ + protected $runtimePath = ''; + + /** + * 路由定义目录 + * @var string + */ + protected $routePath = ''; + + /** + * 配置后缀 + * @var string + */ + protected $configExt = '.php'; + + /** + * 应用初始化器 + * @var array + */ + protected $initializers = [ + Error::class, + RegisterService::class, + BootService::class, + ]; + + /** + * 注册的系统服务 + * @var array + */ + protected $services = []; + + /** + * 初始化 + * @var bool + */ + protected $initialized = false; + + /** + * 容器绑定标识 + * @var array + */ + protected $bind = [ + 'app' => App::class, + 'cache' => Cache::class, + 'config' => Config::class, + 'console' => Console::class, + 'cookie' => Cookie::class, + 'db' => Db::class, + 'env' => Env::class, + 'event' => Event::class, + 'http' => Http::class, + 'lang' => Lang::class, + 'log' => Log::class, + 'middleware' => Middleware::class, + 'request' => Request::class, + 'response' => Response::class, + 'route' => Route::class, + 'session' => Session::class, + 'validate' => Validate::class, + 'view' => View::class, + 'filesystem' => Filesystem::class, + 'think\DbManager' => Db::class, + 'think\LogManager' => Log::class, + 'think\CacheManager' => Cache::class, + + // 接口依赖注入 + 'Psr\Log\LoggerInterface' => Log::class, + ]; + + /** + * 架构方法 + * @access public + * @param string $rootPath 应用根目录 + */ + public function __construct(string $rootPath = '') + { + $this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR; + $this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath(); + $this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR; + $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; + + if (is_file($this->appPath . 'provider.php')) { + $this->bind(include $this->appPath . 'provider.php'); + } + + static::setInstance($this); + + $this->instance('app', $this); + $this->instance('think\Container', $this); + } + + /** + * 注册服务 + * @access public + * @param Service|string $service 服务 + * @param bool $force 强制重新注册 + * @return Service|null + */ + public function register($service, bool $force = false) + { + $registered = $this->getService($service); + + if ($registered && !$force) { + return $registered; + } + + if (is_string($service)) { + $service = new $service($this); + } + + if (method_exists($service, 'register')) { + $service->register(); + } + + if (property_exists($service, 'bind')) { + $this->bind($service->bind); + } + + $this->services[] = $service; + } + + /** + * 执行服务 + * @access public + * @param Service $service 服务 + * @return mixed + */ + public function bootService($service) + { + if (method_exists($service, 'boot')) { + return $this->invoke([$service, 'boot']); + } + } + + /** + * 获取服务 + * @param string|Service $service + * @return Service|null + */ + public function getService($service) + { + $name = is_string($service) ? $service : get_class($service); + return array_values(array_filter($this->services, function ($value) use ($name) { + return $value instanceof $name; + }, ARRAY_FILTER_USE_BOTH))[0] ?? null; + } + + /** + * 开启应用调试模式 + * @access public + * @param bool $debug 开启应用调试模式 + * @return $this + */ + public function debug(bool $debug = true) + { + $this->appDebug = $debug; + return $this; + } + + /** + * 是否为调试模式 + * @access public + * @return bool + */ + public function isDebug(): bool + { + return $this->appDebug; + } + + /** + * 设置应用命名空间 + * @access public + * @param string $namespace 应用命名空间 + * @return $this + */ + public function setNamespace(string $namespace) + { + + $this->namespace = $namespace; + return $this; + } + + /** + * 获取应用类库命名空间 + * @access public + * @return string + */ + public function getNamespace(): string + { + return $this->namespace; + } + + /** + * 获取框架版本 + * @access public + * @return string + */ + public function version(): string + { + return static::VERSION; + } + + /** + * 获取应用根目录 + * @access public + * @return string + */ + public function getRootPath(): string + { + return $this->rootPath; + } + + /** + * 获取应用基础目录 + * @access public + * @return string + */ + public function getBasePath(): string + { + return $this->rootPath . 'app' . DIRECTORY_SEPARATOR; + } + + /** + * 获取当前应用目录 + * @access public + * @return string + */ + public function getAppPath(): string + { + return $this->appPath; + } + + /** + * 设置应用目录 + * @param string $path 应用目录 + */ + public function setAppPath(string $path) + { + $this->appPath = $path; + } + + /** + * 获取应用运行时目录 + * @access public + * @return string + */ + public function getRuntimePath(): string + { + return $this->runtimePath; + } + + /** + * 设置runtime目录 + * @param string $path 定义目录 + */ + public function setRuntimePath(string $path): void + { + $this->runtimePath = $path; + } + + /** + * 获取核心框架目录 + * @access public + * @return string + */ + public function getThinkPath(): string + { + return $this->thinkPath; + } + + /** + * 获取应用配置目录 + * @access public + * @return string + */ + public function getConfigPath(): string + { + return $this->rootPath . 'config' . DIRECTORY_SEPARATOR; + } + + /** + * 获取配置后缀 + * @access public + * @return string + */ + public function getConfigExt(): string + { + return $this->configExt; + } + + /** + * 获取应用开启时间 + * @access public + * @return float + */ + public function getBeginTime(): float + { + return $this->beginTime; + } + + /** + * 获取应用初始内存占用 + * @access public + * @return integer + */ + public function getBeginMem(): int + { + return $this->beginMem; + } + + /** + * 初始化应用 + * @access public + * @return $this + */ + public function initialize() + { + $this->initialized = true; + + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); + + // 加载环境变量 + if (is_file($this->rootPath . '.env')) { + $this->env->load($this->rootPath . '.env'); + } + + $this->configExt = $this->env->get('config_ext', '.php'); + + $this->debugModeInit(); + + // 加载全局初始化文件 + $this->load(); + + // 加载框架默认语言包 + $langSet = $this->lang->defaultLangSet(); + + $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php'); + + // 加载应用默认语言包 + $this->loadLangPack($langSet); + + // 监听AppInit + $this->event->trigger(AppInit::class); + + date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai')); + + // 初始化 + foreach ($this->initializers as $initializer) { + $this->make($initializer)->init($this); + } + + return $this; + } + + /** + * 是否初始化过 + * @return bool + */ + public function initialized() + { + return $this->initialized; + } + + /** + * 加载语言包 + * @param string $langset 语言 + * @return void + */ + public function loadLangPack($langset) + { + if (empty($langset)) { + return; + } + + // 加载系统语言包 + $files = glob($this->appPath . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*'); + $this->lang->load($files); + + // 加载扩展(自定义)语言包 + $list = $this->config->get('lang.extend_list', []); + + if (isset($list[$langset])) { + $this->lang->load($list[$langset]); + } + } + + /** + * 引导应用 + * @access public + * @return void + */ + public function boot(): void + { + array_walk($this->services, function ($service) { + $this->bootService($service); + }); + } + + /** + * 加载应用文件和配置 + * @access protected + * @return void + */ + protected function load(): void + { + $appPath = $this->getAppPath(); + + if (is_file($appPath . 'common.php')) { + include_once $appPath . 'common.php'; + } + + include_once $this->thinkPath . 'helper.php'; + + $configPath = $this->getConfigPath(); + + $files = []; + + if (is_dir($configPath)) { + $files = glob($configPath . '*' . $this->configExt); + } + + foreach ($files as $file) { + $this->config->load($file, pathinfo($file, PATHINFO_FILENAME)); + } + + if (is_file($appPath . 'event.php')) { + $this->loadEvent(include $appPath . 'event.php'); + } + + if (is_file($appPath . 'service.php')) { + $services = include $appPath . 'service.php'; + foreach ($services as $service) { + $this->register($service); + } + } + } + + /** + * 调试模式设置 + * @access protected + * @return void + */ + protected function debugModeInit(): void + { + // 应用调试模式 + if (!$this->appDebug) { + $this->appDebug = $this->env->get('app_debug') ? true : false; + ini_set('display_errors', 'Off'); + } + + if (!$this->runningInConsole()) { + //重新申请一块比较大的buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + ob_start(); + if (!empty($output)) { + echo $output; + } + } + } + + /** + * 注册应用事件 + * @access protected + * @param array $event 事件数据 + * @return void + */ + public function loadEvent(array $event): void + { + if (isset($event['bind'])) { + $this->event->bind($event['bind']); + } + + if (isset($event['listen'])) { + $this->event->listenEvents($event['listen']); + } + + if (isset($event['subscribe'])) { + $this->event->subscribe($event['subscribe']); + } + } + + /** + * 解析应用类的类名 + * @access public + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @return string + */ + public function parseClass(string $layer, string $name): string + { + $name = str_replace(['/', '.'], '\\', $name); + $array = explode('\\', $name); + $class = Str::studly(array_pop($array)); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return $this->namespace . '\\' . $layer . '\\' . $path . $class; + } + + /** + * 是否运行在命令行下 + * @return bool + */ + public function runningInConsole() + { + return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg'; + } + + /** + * 获取应用根目录 + * @access protected + * @return string + */ + protected function getDefaultRootPath(): string + { + $path = dirname(dirname(dirname(dirname($this->thinkPath)))); + + return $path . DIRECTORY_SEPARATOR; + } + +} diff --git a/vendor/topthink/framework/src/think/Cache.php b/vendor/topthink/framework/src/think/Cache.php new file mode 100644 index 0000000..4bc99c2 --- /dev/null +++ b/vendor/topthink/framework/src/think/Cache.php @@ -0,0 +1,197 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Psr\SimpleCache\CacheInterface; +use think\cache\Driver; +use think\cache\TagSet; +use think\exception\InvalidArgumentException; +use think\helper\Arr; + +/** + * 缓存管理类 + * @mixin Driver + * @mixin \think\cache\driver\File + */ +class Cache extends Manager implements CacheInterface +{ + + protected $namespace = '\\think\\cache\\driver\\'; + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->getConfig('default'); + } + + /** + * 获取缓存配置 + * @access public + * @param null|string $name 名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (!is_null($name)) { + return $this->app->config->get('cache.' . $name, $default); + } + + return $this->app->config->get('cache'); + } + + /** + * 获取驱动配置 + * @param string $store + * @param string $name + * @param null $default + * @return array + */ + public function getStoreConfig(string $store, string $name = null, $default = null) + { + if ($config = $this->getConfig("stores.{$store}")) { + return Arr::get($config, $name, $default); + } + + throw new \InvalidArgumentException("Store [$store] not found."); + } + + protected function resolveType(string $name) + { + return $this->getStoreConfig($name, 'type', 'file'); + } + + protected function resolveConfig(string $name) + { + return $this->getStoreConfig($name); + } + + /** + * 连接或者切换缓存 + * @access public + * @param string $name 连接配置名 + * @return Driver + */ + public function store(string $name = null) + { + return $this->driver($name); + } + + /** + * 清空缓冲池 + * @access public + * @return bool + */ + public function clear(): bool + { + return $this->store()->clear(); + } + + /** + * 读取缓存 + * @access public + * @param string $key 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($key, $default = null) + { + return $this->store()->get($key, $default); + } + + /** + * 写入缓存 + * @access public + * @param string $key 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $ttl 有效时间 0为永久 + * @return bool + */ + public function set($key, $value, $ttl = null): bool + { + return $this->store()->set($key, $value, $ttl); + } + + /** + * 删除缓存 + * @access public + * @param string $key 缓存变量名 + * @return bool + */ + public function delete($key): bool + { + return $this->store()->delete($key); + } + + /** + * 读取缓存 + * @access public + * @param iterable $keys 缓存变量名 + * @param mixed $default 默认值 + * @return iterable + * @throws InvalidArgumentException + */ + public function getMultiple($keys, $default = null): iterable + { + return $this->store()->getMultiple($keys, $default); + } + + /** + * 写入缓存 + * @access public + * @param iterable $values 缓存数据 + * @param null|int|\DateInterval $ttl 有效时间 0为永久 + * @return bool + */ + public function setMultiple($values, $ttl = null): bool + { + return $this->store()->setMultiple($values, $ttl); + } + + /** + * 删除缓存 + * @access public + * @param iterable $keys 缓存变量名 + * @return bool + * @throws InvalidArgumentException + */ + public function deleteMultiple($keys): bool + { + return $this->store()->deleteMultiple($keys); + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $key 缓存变量名 + * @return bool + */ + public function has($key): bool + { + return $this->store()->has($key); + } + + /** + * 缓存标签 + * @access public + * @param string|array $name 标签名 + * @return TagSet + */ + public function tag($name): TagSet + { + return $this->store()->tag($name); + } +} diff --git a/vendor/topthink/framework/src/think/Config.php b/vendor/topthink/framework/src/think/Config.php new file mode 100644 index 0000000..78933ad --- /dev/null +++ b/vendor/topthink/framework/src/think/Config.php @@ -0,0 +1,193 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 配置管理类 + * @package think + */ +class Config +{ + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 配置文件目录 + * @var string + */ + protected $path; + + /** + * 配置文件后缀 + * @var string + */ + protected $ext; + + /** + * 构造方法 + * @access public + */ + public function __construct(string $path = null, string $ext = '.php') + { + $this->path = $path ?: ''; + $this->ext = $ext; + } + + public static function __make(App $app) + { + $path = $app->getConfigPath(); + $ext = $app->getConfigExt(); + + return new static($path, $ext); + } + + /** + * 加载配置文件(多种格式) + * @access public + * @param string $file 配置文件名 + * @param string $name 一级配置名 + * @return array + */ + public function load(string $file, string $name = ''): array + { + if (is_file($file)) { + $filename = $file; + } elseif (is_file($this->path . $file . $this->ext)) { + $filename = $this->path . $file . $this->ext; + } + + if (isset($filename)) { + return $this->parse($filename, $name); + } + + return $this->config; + } + + /** + * 解析配置文件 + * @access public + * @param string $file 配置文件名 + * @param string $name 一级配置名 + * @return array + */ + protected function parse(string $file, string $name): array + { + $type = pathinfo($file, PATHINFO_EXTENSION); + $config = []; + switch ($type) { + case 'php': + $config = include $file; + break; + case 'yml': + case 'yaml': + if (function_exists('yaml_parse_file')) { + $config = yaml_parse_file($file); + } + break; + case 'ini': + $config = parse_ini_file($file, true, INI_SCANNER_TYPED) ?: []; + break; + case 'json': + $config = json_decode(file_get_contents($file), true); + break; + } + + return is_array($config) ? $this->set($config, strtolower($name)) : []; + } + + /** + * 检测配置是否存在 + * @access public + * @param string $name 配置参数名(支持多级配置 .号分割) + * @return bool + */ + public function has(string $name): bool + { + return !is_null($this->get($name)); + } + + /** + * 获取一级配置 + * @access protected + * @param string $name 一级配置名 + * @return array + */ + protected function pull(string $name): array + { + $name = strtolower($name); + + return $this->config[$name] ?? []; + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持多级配置 .号分割) + * @param mixed $default 默认值 + * @return mixed + */ + public function get(string $name = null, $default = null) + { + // 无参数时获取所有 + if (empty($name)) { + return $this->config; + } + + if (false === strpos($name, '.')) { + return $this->pull($name); + } + + $name = explode('.', $name); + $name[0] = strtolower($name[0]); + $config = $this->config; + + // 按.拆分成多维数组进行判断 + foreach ($name as $val) { + if (isset($config[$val])) { + $config = $config[$val]; + } else { + return $default; + } + } + + return $config; + } + + /** + * 设置配置参数 name为数组则为批量设置 + * @access public + * @param array $config 配置参数 + * @param string $name 配置名 + * @return array + */ + public function set(array $config, string $name = null): array + { + if (!empty($name)) { + if (isset($this->config[$name])) { + $result = array_merge($this->config[$name], $config); + } else { + $result = $config; + } + + $this->config[$name] = $result; + } else { + $result = $this->config = array_merge($this->config, array_change_key_case($config)); + } + + return $result; + } + +} diff --git a/vendor/topthink/framework/src/think/Console.php b/vendor/topthink/framework/src/think/Console.php new file mode 100644 index 0000000..a1398d5 --- /dev/null +++ b/vendor/topthink/framework/src/think/Console.php @@ -0,0 +1,732 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use InvalidArgumentException; +use LogicException; +use think\console\Command; +use think\console\command\Clear; +use think\console\command\Help; +use think\console\command\Help as HelpCommand; +use think\console\command\Lists; +use think\console\command\make\Command as MakeCommand; +use think\console\command\make\Controller; +use think\console\command\make\Event; +use think\console\command\make\Listener; +use think\console\command\make\Middleware; +use think\console\command\make\Model; +use think\console\command\make\Service; +use think\console\command\make\Subscribe; +use think\console\command\make\Validate; +use think\console\command\optimize\Route; +use think\console\command\optimize\Schema; +use think\console\command\RouteList; +use think\console\command\RunServer; +use think\console\command\ServiceDiscover; +use think\console\command\VendorPublish; +use think\console\command\Version; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\console\output\driver\Buffer; + +/** + * 控制台应用管理类 + */ +class Console +{ + + protected $app; + + /** @var Command[] */ + protected $commands = []; + + protected $wantHelps = false; + + protected $catchExceptions = true; + protected $autoExit = true; + protected $definition; + protected $defaultCommand = 'list'; + + protected $defaultCommands = [ + 'help' => Help::class, + 'list' => Lists::class, + 'clear' => Clear::class, + 'make:command' => MakeCommand::class, + 'make:controller' => Controller::class, + 'make:model' => Model::class, + 'make:middleware' => Middleware::class, + 'make:validate' => Validate::class, + 'make:event' => Event::class, + 'make:listener' => Listener::class, + 'make:service' => Service::class, + 'make:subscribe' => Subscribe::class, + 'optimize:route' => Route::class, + 'optimize:schema' => Schema::class, + 'run' => RunServer::class, + 'version' => Version::class, + 'route:list' => RouteList::class, + 'service:discover' => ServiceDiscover::class, + 'vendor:publish' => VendorPublish::class, + ]; + + /** + * 启动器 + * @var array + */ + protected static $startCallbacks = []; + + public function __construct(App $app) + { + $this->app = $app; + + if (!$this->app->initialized()) { + $this->app->initialize(); + } + + $this->definition = $this->getDefaultInputDefinition(); + + //加载指令 + $this->loadCommands(); + + $this->start(); + } + + /** + * 添加初始化器 + * @param Closure $callback + */ + public static function starting(Closure $callback): void + { + static::$startCallbacks[] = $callback; + } + + /** + * 清空启动器 + */ + public static function flushStartCallbacks(): void + { + static::$startCallbacks = []; + } + + /** + * 设置执行用户 + * @param $user + */ + public static function setUser(string $user): void + { + if (extension_loaded('posix')) { + $user = posix_getpwnam($user); + + if (!empty($user)) { + posix_setgid($user['gid']); + posix_setuid($user['uid']); + } + } + } + + /** + * 启动 + */ + protected function start(): void + { + foreach (static::$startCallbacks as $callback) { + $callback($this); + } + } + + /** + * 加载指令 + * @access protected + */ + protected function loadCommands(): void + { + $commands = $this->app->config->get('console.commands', []); + $commands = array_merge($this->defaultCommands, $commands); + + $this->addCommands($commands); + } + + /** + * @access public + * @param string $command + * @param array $parameters + * @param string $driver + * @return Output|Buffer + */ + public function call(string $command, array $parameters = [], string $driver = 'buffer') + { + array_unshift($parameters, $command); + + $input = new Input($parameters); + $output = new Output($driver); + + $this->setCatchExceptions(false); + $this->find($command)->run($input, $output); + + return $output; + } + + /** + * 执行当前的指令 + * @access public + * @return int + * @throws \Exception + * @api + */ + public function run() + { + $input = new Input(); + $output = new Output(); + + $this->configureIO($input, $output); + + try { + $exitCode = $this->doRun($input, $output); + } catch (\Exception $e) { + if (!$this->catchExceptions) { + throw $e; + } + + $output->renderException($e); + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if (0 === $exitCode) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + + exit($exitCode); + } + + return $exitCode; + } + + /** + * 执行指令 + * @access public + * @param Input $input + * @param Output $output + * @return int + */ + public function doRun(Input $input, Output $output) + { + if (true === $input->hasParameterOption(['--version', '-V'])) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + $name = $this->getCommandName($input); + + if (true === $input->hasParameterOption(['--help', '-h'])) { + if (!$name) { + $name = 'help'; + $input = new Input(['help']); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $input = new Input([$this->defaultCommand]); + } + + $command = $this->find($name); + + return $this->doRunCommand($command, $input, $output); + } + + /** + * 设置输入参数定义 + * @access public + * @param InputDefinition $definition + */ + public function setDefinition(InputDefinition $definition): void + { + $this->definition = $definition; + } + + /** + * 获取输入参数定义 + * @access public + * @return InputDefinition The InputDefinition instance + */ + public function getDefinition(): InputDefinition + { + return $this->definition; + } + + /** + * Gets the help message. + * @access public + * @return string A help message. + */ + public function getHelp(): string + { + return $this->getLongVersion(); + } + + /** + * 是否捕获异常 + * @access public + * @param bool $boolean + * @api + */ + public function setCatchExceptions(bool $boolean): void + { + $this->catchExceptions = $boolean; + } + + /** + * 是否自动退出 + * @access public + * @param bool $boolean + * @api + */ + public function setAutoExit(bool $boolean): void + { + $this->autoExit = $boolean; + } + + /** + * 获取完整的版本号 + * @access public + * @return string + */ + public function getLongVersion(): string + { + if ($this->app->version()) { + return sprintf('version %s', $this->app->version()); + } + + return 'Console Tool'; + } + + /** + * 添加指令集 + * @access public + * @param array $commands + */ + public function addCommands(array $commands): void + { + foreach ($commands as $key => $command) { + if (is_subclass_of($command, Command::class)) { + // 注册指令 + $this->addCommand($command, is_numeric($key) ? '' : $key); + } + } + } + + /** + * 添加一个指令 + * @access public + * @param string|Command $command 指令对象或者指令类名 + * @param string $name 指令名 留空则自动获取 + * @return Command|void + */ + public function addCommand($command, string $name = '') + { + if ($name) { + $this->commands[$name] = $command; + return; + } + + if (is_string($command)) { + $command = $this->app->invokeClass($command); + } + + $command->setConsole($this); + + if (!$command->isEnabled()) { + $command->setConsole(null); + return; + } + + $command->setApp($this->app); + + if (null === $command->getDefinition()) { + throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * 获取指令 + * @access public + * @param string $name 指令名称 + * @return Command + * @throws InvalidArgumentException + */ + public function getCommand(string $name): Command + { + if (!isset($this->commands[$name])) { + throw new InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); + } + + $command = $this->commands[$name]; + + if (is_string($command)) { + $command = $this->app->invokeClass($command); + /** @var Command $command */ + $command->setConsole($this); + $command->setApp($this->app); + } + + if ($this->wantHelps) { + $this->wantHelps = false; + + /** @var HelpCommand $helpCommand */ + $helpCommand = $this->getCommand('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * 某个指令是否存在 + * @access public + * @param string $name 指令名称 + * @return bool + */ + public function hasCommand(string $name): bool + { + return isset($this->commands[$name]); + } + + /** + * 获取所有的命名空间 + * @access public + * @return array + */ + public function getNamespaces(): array + { + $namespaces = []; + foreach ($this->commands as $key => $command) { + if (is_string($command)) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($key)); + } else { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); + + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); + } + } + } + + return array_values(array_unique(array_filter($namespaces))); + } + + /** + * 查找注册命名空间中的名称或缩写。 + * @access public + * @param string $namespace + * @return string + * @throws InvalidArgumentException + */ + public function findNamespace(string $namespace): string + { + $allNamespaces = $this->getNamespaces(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $namespace); + $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new InvalidArgumentException($message); + } + + $exact = in_array($namespace, $namespaces, true); + if (count($namespaces) > 1 && !$exact) { + throw new InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces)))); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * 查找指令 + * @access public + * @param string $name 名称或者别名 + * @return Command + * @throws InvalidArgumentException + */ + public function find(string $name): Command + { + $allCommands = array_keys($this->commands); + + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $name); + + $commands = preg_grep('{^' . $expr . '}', $allCommands); + + if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) { + if (false !== $pos = strrpos($name, ':')) { + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new InvalidArgumentException($message); + } + + $exact = in_array($name, $commands, true); + if (count($commands) > 1 && !$exact) { + $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); + + throw new InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); + } + + return $this->getCommand($exact ? $name : reset($commands)); + } + + /** + * 获取所有的指令 + * @access public + * @param string $namespace 命名空间 + * @return Command[] + * @api + */ + public function all(string $namespace = null): array + { + if (null === $namespace) { + return $this->commands; + } + + $commands = []; + foreach ($this->commands as $name => $command) { + if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) { + $commands[$name] = $command; + } + } + + return $commands; + } + + /** + * 配置基于用户的参数和选项的输入和输出实例。 + * @access protected + * @param Input $input 输入实例 + * @param Output $output 输出实例 + */ + protected function configureIO(Input $input, Output $output): void + { + if (true === $input->hasParameterOption(['--ansi'])) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(['--no-ansi'])) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(['--no-interaction', '-n'])) { + $input->setInteractive(false); + } + + if (true === $input->hasParameterOption(['--quiet', '-q'])) { + $output->setVerbosity(Output::VERBOSITY_QUIET); + } elseif ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) { + $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE); + } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { + $output->setVerbosity(Output::VERBOSITY_VERBOSE); + } + } + + /** + * 执行指令 + * @access protected + * @param Command $command 指令实例 + * @param Input $input 输入实例 + * @param Output $output 输出实例 + * @return int + * @throws \Exception + */ + protected function doRunCommand(Command $command, Input $input, Output $output) + { + return $command->run($input, $output); + } + + /** + * 获取指令的基础名称 + * @access protected + * @param Input $input + * @return string + */ + protected function getCommandName(Input $input): string + { + return $input->getFirstArgument() ?: ''; + } + + /** + * 获取默认输入定义 + * @access protected + * @return InputDefinition + */ + protected function getDefaultInputDefinition(): InputDefinition + { + return new InputDefinition([ + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), + new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + ]); + } + + /** + * 获取可能的建议 + * @access private + * @param array $abbrevs + * @return string + */ + private function getAbbreviationSuggestions(array $abbrevs): string + { + return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); + } + + /** + * 返回命名空间部分 + * @access public + * @param string $name 指令 + * @param int $limit 部分的命名空间的最大数量 + * @return string + */ + public function extractNamespace(string $name, int $limit = 0): string + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', 0 === $limit ? $parts : array_slice($parts, 0, $limit)); + } + + /** + * 查找可替代的建议 + * @access private + * @param string $name + * @param array|\Traversable $collection + * @return array + */ + private function findAlternatives(string $name, $collection): array + { + $threshold = 1e3; + $alternatives = []; + + $collectionParts = []; + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { + return $lev < 2 * $threshold; + }); + asort($alternatives); + + return array_keys($alternatives); + } + + /** + * 返回所有的命名空间 + * @access private + * @param string $name + * @return array + */ + private function extractAllNamespaces(string $name): array + { + $parts = explode(':', $name, -1); + $namespaces = []; + + foreach ($parts as $part) { + if (count($namespaces)) { + $namespaces[] = end($namespaces) . ':' . $part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + +} diff --git a/vendor/topthink/framework/src/think/Container.php b/vendor/topthink/framework/src/think/Container.php new file mode 100644 index 0000000..12ebe2f --- /dev/null +++ b/vendor/topthink/framework/src/think/Container.php @@ -0,0 +1,551 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Closure; +use Countable; +use InvalidArgumentException; +use IteratorAggregate; +use Psr\Container\ContainerInterface; +use ReflectionClass; +use ReflectionException; +use ReflectionFunction; +use ReflectionFunctionAbstract; +use ReflectionMethod; +use think\exception\ClassNotFoundException; +use think\exception\FuncNotFoundException; +use think\helper\Str; + +/** + * 容器管理类 支持PSR-11 + */ +class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable +{ + /** + * 容器对象实例 + * @var Container|Closure + */ + protected static $instance; + + /** + * 容器中的对象实例 + * @var array + */ + protected $instances = []; + + /** + * 容器绑定标识 + * @var array + */ + protected $bind = []; + + /** + * 容器回调 + * @var array + */ + protected $invokeCallback = []; + + /** + * 获取当前容器的实例(单例) + * @access public + * @return static + */ + public static function getInstance() + { + if (is_null(static::$instance)) { + static::$instance = new static; + } + + if (static::$instance instanceof Closure) { + return (static::$instance)(); + } + + return static::$instance; + } + + /** + * 设置当前容器的实例 + * @access public + * @param object|Closure $instance + * @return void + */ + public static function setInstance($instance): void + { + static::$instance = $instance; + } + + /** + * 注册一个容器对象回调 + * + * @param string|Closure $abstract + * @param Closure|null $callback + * @return void + */ + public function resolving($abstract, Closure $callback = null): void + { + if ($abstract instanceof Closure) { + $this->invokeCallback['*'][] = $abstract; + return; + } + + $abstract = $this->getAlias($abstract); + + $this->invokeCallback[$abstract][] = $callback; + } + + /** + * 获取容器中的对象实例 不存在则创建 + * @access public + * @param string $abstract 类名或者标识 + * @param array|true $vars 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public static function pull(string $abstract, array $vars = [], bool $newInstance = false) + { + return static::getInstance()->make($abstract, $vars, $newInstance); + } + + /** + * 获取容器中的对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @return object + */ + public function get($abstract) + { + if ($this->has($abstract)) { + return $this->make($abstract); + } + + throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract); + } + + /** + * 绑定一个类、闭包、实例、接口实现到容器 + * @access public + * @param string|array $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return $this + */ + public function bind($abstract, $concrete = null) + { + if (is_array($abstract)) { + foreach ($abstract as $key => $val) { + $this->bind($key, $val); + } + } elseif ($concrete instanceof Closure) { + $this->bind[$abstract] = $concrete; + } elseif (is_object($concrete)) { + $this->instance($abstract, $concrete); + } else { + $abstract = $this->getAlias($abstract); + + $this->bind[$abstract] = $concrete; + } + + return $this; + } + + /** + * 根据别名获取真实类名 + * @param string $abstract + * @return string + */ + public function getAlias(string $abstract): string + { + if (isset($this->bind[$abstract])) { + $bind = $this->bind[$abstract]; + + if (is_string($bind)) { + return $this->getAlias($bind); + } + } + + return $abstract; + } + + /** + * 绑定一个类实例到容器 + * @access public + * @param string $abstract 类名或者标识 + * @param object $instance 类的实例 + * @return $this + */ + public function instance(string $abstract, $instance) + { + $abstract = $this->getAlias($abstract); + + $this->instances[$abstract] = $instance; + + return $this; + } + + /** + * 判断容器中是否存在类及标识 + * @access public + * @param string $abstract 类名或者标识 + * @return bool + */ + public function bound(string $abstract): bool + { + return isset($this->bind[$abstract]) || isset($this->instances[$abstract]); + } + + /** + * 判断容器中是否存在类及标识 + * @access public + * @param string $name 类名或者标识 + * @return bool + */ + public function has($name): bool + { + return $this->bound($name); + } + + /** + * 判断容器中是否存在对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @return bool + */ + public function exists(string $abstract): bool + { + $abstract = $this->getAlias($abstract); + + return isset($this->instances[$abstract]); + } + + /** + * 创建类的实例 已经存在则直接获取 + * @access public + * @param string $abstract 类名或者标识 + * @param array $vars 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return mixed + */ + public function make(string $abstract, array $vars = [], bool $newInstance = false) + { + $abstract = $this->getAlias($abstract); + + if (isset($this->instances[$abstract]) && !$newInstance) { + return $this->instances[$abstract]; + } + + if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) { + $object = $this->invokeFunction($this->bind[$abstract], $vars); + } else { + $object = $this->invokeClass($abstract, $vars); + } + + if (!$newInstance) { + $this->instances[$abstract] = $object; + } + + return $object; + } + + /** + * 删除容器中的对象实例 + * @access public + * @param string $name 类名或者标识 + * @return void + */ + public function delete($name) + { + $name = $this->getAlias($name); + + if (isset($this->instances[$name])) { + unset($this->instances[$name]); + } + } + + /** + * 执行函数或者闭包方法 支持参数调用 + * @access public + * @param string|Closure $function 函数或者闭包 + * @param array $vars 参数 + * @return mixed + */ + public function invokeFunction($function, array $vars = []) + { + try { + $reflect = new ReflectionFunction($function); + } catch (ReflectionException $e) { + throw new FuncNotFoundException("function not exists: {$function}()", $function, $e); + } + + $args = $this->bindParams($reflect, $vars); + + return $function(...$args); + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param mixed $method 方法 + * @param array $vars 参数 + * @param bool $accessible 设置是否可访问 + * @return mixed + */ + public function invokeMethod($method, array $vars = [], bool $accessible = false) + { + if (is_array($method)) { + [$class, $method] = $method; + + $class = is_object($class) ? $class : $this->invokeClass($class); + } else { + // 静态方法 + [$class, $method] = explode('::', $method); + } + + try { + $reflect = new ReflectionMethod($class, $method); + } catch (ReflectionException $e) { + $class = is_object($class) ? get_class($class) : $class; + throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e); + } + + $args = $this->bindParams($reflect, $vars); + + if ($accessible) { + $reflect->setAccessible($accessible); + } + + return $reflect->invokeArgs(is_object($class) ? $class : null, $args); + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param object $instance 对象实例 + * @param mixed $reflect 反射类 + * @param array $vars 参数 + * @return mixed + */ + public function invokeReflectMethod($instance, $reflect, array $vars = []) + { + $args = $this->bindParams($reflect, $vars); + + return $reflect->invokeArgs($instance, $args); + } + + /** + * 调用反射执行callable 支持参数绑定 + * @access public + * @param mixed $callable + * @param array $vars 参数 + * @param bool $accessible 设置是否可访问 + * @return mixed + */ + public function invoke($callable, array $vars = [], bool $accessible = false) + { + if ($callable instanceof Closure) { + return $this->invokeFunction($callable, $vars); + } elseif (is_string($callable) && false === strpos($callable, '::')) { + return $this->invokeFunction($callable, $vars); + } else { + return $this->invokeMethod($callable, $vars, $accessible); + } + } + + /** + * 调用反射执行类的实例化 支持依赖注入 + * @access public + * @param string $class 类名 + * @param array $vars 参数 + * @return mixed + */ + public function invokeClass(string $class, array $vars = []) + { + try { + $reflect = new ReflectionClass($class); + } catch (ReflectionException $e) { + throw new ClassNotFoundException('class not exists: ' . $class, $class, $e); + } + + if ($reflect->hasMethod('__make')) { + $method = $reflect->getMethod('__make'); + if ($method->isPublic() && $method->isStatic()) { + $args = $this->bindParams($method, $vars); + return $method->invokeArgs(null, $args); + } + } + + $constructor = $reflect->getConstructor(); + + $args = $constructor ? $this->bindParams($constructor, $vars) : []; + + $object = $reflect->newInstanceArgs($args); + + $this->invokeAfter($class, $object); + + return $object; + } + + /** + * 执行invokeClass回调 + * @access protected + * @param string $class 对象类名 + * @param object $object 容器对象实例 + * @return void + */ + protected function invokeAfter(string $class, $object): void + { + if (isset($this->invokeCallback['*'])) { + foreach ($this->invokeCallback['*'] as $callback) { + $callback($object, $this); + } + } + + if (isset($this->invokeCallback[$class])) { + foreach ($this->invokeCallback[$class] as $callback) { + $callback($object, $this); + } + } + } + + /** + * 绑定参数 + * @access protected + * @param ReflectionFunctionAbstract $reflect 反射类 + * @param array $vars 参数 + * @return array + */ + protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array + { + if ($reflect->getNumberOfParameters() == 0) { + return []; + } + + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + $params = $reflect->getParameters(); + $args = []; + + foreach ($params as $param) { + $name = $param->getName(); + $lowerName = Str::snake($name); + $class = $param->getClass(); + + if ($class) { + $args[] = $this->getObjectParam($class->getName(), $vars); + } elseif (1 == $type && !empty($vars)) { + $args[] = array_shift($vars); + } elseif (0 == $type && isset($vars[$name])) { + $args[] = $vars[$name]; + } elseif (0 == $type && isset($vars[$lowerName])) { + $args[] = $vars[$lowerName]; + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } else { + throw new InvalidArgumentException('method param miss:' . $name); + } + } + + return $args; + } + + /** + * 创建工厂对象实例 + * @param string $name 工厂类名 + * @param string $namespace 默认命名空间 + * @param array $args + * @return mixed + * @deprecated + * @access public + */ + public static function factory(string $name, string $namespace = '', ...$args) + { + $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name); + + return Container::getInstance()->invokeClass($class, $args); + } + + /** + * 获取对象类型的参数值 + * @access protected + * @param string $className 类名 + * @param array $vars 参数 + * @return mixed + */ + protected function getObjectParam(string $className, array &$vars) + { + $array = $vars; + $value = array_shift($array); + + if ($value instanceof $className) { + $result = $value; + array_shift($vars); + } else { + $result = $this->make($className); + } + + return $result; + } + + public function __set($name, $value) + { + $this->bind($name, $value); + } + + public function __get($name) + { + return $this->get($name); + } + + public function __isset($name): bool + { + return $this->exists($name); + } + + public function __unset($name) + { + $this->delete($name); + } + + public function offsetExists($key) + { + return $this->exists($key); + } + + public function offsetGet($key) + { + return $this->make($key); + } + + public function offsetSet($key, $value) + { + $this->bind($key, $value); + } + + public function offsetUnset($key) + { + $this->delete($key); + } + + //Countable + public function count() + { + return count($this->instances); + } + + //IteratorAggregate + public function getIterator() + { + return new ArrayIterator($this->instances); + } +} diff --git a/vendor/topthink/framework/src/think/Cookie.php b/vendor/topthink/framework/src/think/Cookie.php new file mode 100644 index 0000000..6eb85b6 --- /dev/null +++ b/vendor/topthink/framework/src/think/Cookie.php @@ -0,0 +1,230 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use DateTimeInterface; + +/** + * Cookie管理类 + * @package think + */ +class Cookie +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // samesite 设置,支持 'strict' 'lax' + 'samesite' => '', + ]; + + /** + * Cookie写入数据 + * @var array + */ + protected $cookie = []; + + /** + * 当前Request对象 + * @var Request + */ + protected $request; + + /** + * 构造方法 + * @access public + */ + public function __construct(Request $request, array $config = []) + { + $this->request = $request; + $this->config = array_merge($this->config, array_change_key_case($config)); + } + + public static function __make(Request $request, Config $config) + { + return new static($request, $config->get('cookie')); + } + + /** + * 获取cookie + * @access public + * @param mixed $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function get(string $name = '', $default = null) + { + return $this->request->cookie($name, $default); + } + + /** + * 是否存在Cookie参数 + * @access public + * @param string $name 变量名 + * @return bool + */ + public function has(string $name): bool + { + return $this->request->has($name, 'cookie'); + } + + /** + * Cookie 设置 + * + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param mixed $option 可选参数 + * @return void + */ + public function set(string $name, string $value, $option = null): void + { + // 参数设置(会覆盖黙认设置) + if (!is_null($option)) { + if (is_numeric($option) || $option instanceof DateTimeInterface) { + $option = ['expire' => $option]; + } + + $config = array_merge($this->config, array_change_key_case($option)); + } else { + $config = $this->config; + } + + if ($config['expire'] instanceof DateTimeInterface) { + $expire = $config['expire']->getTimestamp(); + } else { + $expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0; + } + + $this->setCookie($name, $value, $expire, $config); + } + + /** + * Cookie 保存 + * + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param int $expire 有效期 + * @param array $option 可选参数 + * @return void + */ + protected function setCookie(string $name, string $value, int $expire, array $option = []): void + { + $this->cookie[$name] = [$value, $expire, $option]; + } + + /** + * 永久保存Cookie数据 + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public function forever(string $name, string $value = '', $option = null): void + { + if (is_null($option) || is_numeric($option)) { + $option = []; + } + + $option['expire'] = 315360000; + + $this->set($name, $value, $option); + } + + /** + * Cookie删除 + * @access public + * @param string $name cookie名称 + * @return void + */ + public function delete(string $name): void + { + $this->setCookie($name, '', time() - 3600, $this->config); + } + + /** + * 获取cookie保存数据 + * @access public + * @return array + */ + public function getCookie(): array + { + return $this->cookie; + } + + /** + * 保存Cookie + * @access public + * @return void + */ + public function save(): void + { + foreach ($this->cookie as $name => $val) { + [$value, $expire, $option] = $val; + + $this->saveCookie( + $name, + $value, + $expire, + $option['path'], + $option['domain'], + $option['secure'] ? true : false, + $option['httponly'] ? true : false, + $option['samesite'] + ); + } + } + + /** + * 保存Cookie + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param int $expire cookie过期时间 + * @param string $path 有效的服务器路径 + * @param string $domain 有效域名/子域名 + * @param bool $secure 是否仅仅通过HTTPS + * @param bool $httponly 仅可通过HTTP访问 + * @param string $samesite 防止CSRF攻击和用户追踪 + * @return void + */ + protected function saveCookie(string $name, string $value, int $expire, string $path, string $domain, bool $secure, bool $httponly, string $samesite): void + { + if (version_compare(PHP_VERSION, '7.3.0', '>=')) { + setcookie($name, $value, [ + 'expires' => $expire, + 'path' => $path, + 'domain' => $domain, + 'secure' => $secure, + 'httponly' => $httponly, + 'samesite' => $samesite, + ]); + } else { + setcookie($name, $value, $expire, $path, $domain, $secure, $httponly); + } + } + +} diff --git a/vendor/topthink/framework/src/think/Db.php b/vendor/topthink/framework/src/think/Db.php new file mode 100644 index 0000000..06b7ff2 --- /dev/null +++ b/vendor/topthink/framework/src/think/Db.php @@ -0,0 +1,114 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 数据库管理类 + * @package think + */ +class Db extends DbManager +{ + /** + * @param Event $event + * @param Config $config + * @param Log $log + * @param Cache $cache + * @return Db + * @codeCoverageIgnore + */ + public static function __make(Event $event, Config $config, Log $log, Cache $cache) + { + $db = new static(); + $db->setConfig($config); + $db->setEvent($event); + $db->setLog($log); + $db->setCache($cache); + $db->triggerSql(); + + return $db; + } + + /** + * 注入模型对象 + * @access public + * @return void + */ + protected function modelMaker() + { + } + + /** + * 设置配置对象 + * @access public + * @param Config $config 配置对象 + * @return void + */ + public function setConfig($config): void + { + $this->config = $config; + } + + /** + * 获取配置参数 + * @access public + * @param string $name 配置参数 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = '', $default = null) + { + if ('' !== $name) { + return $this->config->get('database.' . $name, $default); + } + + return $this->config->get('database', []); + } + + /** + * 设置Event对象 + * @param Event $event + */ + public function setEvent(Event $event): void + { + $this->event = $event; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public function event(string $event, callable $callback): void + { + if ($this->event) { + $this->event->listen('db.' . $event, $callback); + } + } + + /** + * 触发事件 + * @access public + * @param string $event 事件名 + * @param mixed $params 传入参数 + * @param bool $once + * @return mixed + */ + public function trigger(string $event, $params = null, bool $once = false) + { + if ($this->event) { + return $this->event->trigger('db.' . $event, $params, $once); + } + } +} diff --git a/vendor/topthink/framework/src/think/Env.php b/vendor/topthink/framework/src/think/Env.php new file mode 100644 index 0000000..05228aa --- /dev/null +++ b/vendor/topthink/framework/src/think/Env.php @@ -0,0 +1,181 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; + +/** + * Env管理类 + * @package think + */ +class Env implements ArrayAccess +{ + /** + * 环境变量数据 + * @var array + */ + protected $data = []; + + public function __construct() + { + $this->data = $_ENV; + } + + /** + * 读取环境变量定义文件 + * @access public + * @param string $file 环境变量定义文件 + * @return void + */ + public function load(string $file): void + { + $env = parse_ini_file($file, true) ?: []; + $this->set($env); + } + + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get(string $name = null, $default = null) + { + if (is_null($name)) { + return $this->data; + } + + $name = strtoupper(str_replace('.', '_', $name)); + + if (isset($this->data[$name])) { + return $this->data[$name]; + } + + return $this->getEnv($name, $default); + } + + protected function getEnv(string $name, $default = null) + { + $result = getenv('PHP_' . $name); + + if (false === $result) { + return $default; + } + + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } + + if (!isset($this->data[$name])) { + $this->data[$name] = $result; + } + + return $result; + } + + /** + * 设置环境变量值 + * @access public + * @param string|array $env 环境变量 + * @param mixed $value 值 + * @return void + */ + public function set($env, $value = null): void + { + if (is_array($env)) { + $env = array_change_key_case($env, CASE_UPPER); + + foreach ($env as $key => $val) { + if (is_array($val)) { + foreach ($val as $k => $v) { + $this->data[$key . '_' . strtoupper($k)] = $v; + } + } else { + $this->data[$key] = $val; + } + } + } else { + $name = strtoupper(str_replace('.', '_', $env)); + + $this->data[$name] = $value; + } + } + + /** + * 检测是否存在环境变量 + * @access public + * @param string $name 参数名 + * @return bool + */ + public function has(string $name): bool + { + return !is_null($this->get($name)); + } + + /** + * 设置环境变量 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set(string $name, $value): void + { + $this->set($name, $value); + } + + /** + * 获取环境变量 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function __get(string $name) + { + return $this->get($name); + } + + /** + * 检测是否存在环境变量 + * @access public + * @param string $name 参数名 + * @return bool + */ + public function __isset(string $name): bool + { + return $this->has($name); + } + + // ArrayAccess + public function offsetSet($name, $value): void + { + $this->set($name, $value); + } + + public function offsetExists($name): bool + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + throw new Exception('not support: unset'); + } + + public function offsetGet($name) + { + return $this->get($name); + } +} diff --git a/vendor/topthink/framework/src/think/Event.php b/vendor/topthink/framework/src/think/Event.php new file mode 100644 index 0000000..ecb9912 --- /dev/null +++ b/vendor/topthink/framework/src/think/Event.php @@ -0,0 +1,301 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ReflectionClass; +use ReflectionMethod; + +/** + * 事件管理类 + * @package think + */ +class Event +{ + /** + * 监听者 + * @var array + */ + protected $listener = []; + + /** + * 事件别名 + * @var array + */ + protected $bind = [ + 'AppInit' => event\AppInit::class, + 'HttpRun' => event\HttpRun::class, + 'HttpEnd' => event\HttpEnd::class, + 'RouteLoaded' => event\RouteLoaded::class, + 'LogWrite' => event\LogWrite::class, + ]; + + /** + * 是否需要事件响应 + * @var bool + */ + protected $withEvent = true; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 设置是否开启事件响应 + * @access protected + * @param bool $event 是否需要事件响应 + * @return $this + */ + public function withEvent(bool $event) + { + $this->withEvent = $event; + return $this; + } + + /** + * 批量注册事件监听 + * @access public + * @param array $events 事件定义 + * @return $this + */ + public function listenEvents(array $events) + { + if (!$this->withEvent) { + return $this; + } + + foreach ($events as $event => $listeners) { + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + $this->listener[$event] = array_merge($this->listener[$event] ?? [], $listeners); + } + + return $this; + } + + /** + * 注册事件监听 + * @access public + * @param string $event 事件名称 + * @param mixed $listener 监听操作(或者类名) + * @param bool $first 是否优先执行 + * @return $this + */ + public function listen(string $event, $listener, bool $first = false) + { + if (!$this->withEvent) { + return $this; + } + + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + if ($first && isset($this->listener[$event])) { + array_unshift($this->listener[$event], $listener); + } else { + $this->listener[$event][] = $listener; + } + + return $this; + } + + /** + * 是否存在事件监听 + * @access public + * @param string $event 事件名称 + * @return bool + */ + public function hasListener(string $event): bool + { + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + return isset($this->listener[$event]); + } + + /** + * 移除事件监听 + * @access public + * @param string $event 事件名称 + * @return void + */ + public function remove(string $event): void + { + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + unset($this->listener[$event]); + } + + /** + * 指定事件别名标识 便于调用 + * @access public + * @param array $events 事件别名 + * @return $this + */ + public function bind(array $events) + { + $this->bind = array_merge($this->bind, $events); + + return $this; + } + + /** + * 注册事件订阅者 + * @access public + * @param mixed $subscriber 订阅者 + * @return $this + */ + public function subscribe($subscriber) + { + if (!$this->withEvent) { + return $this; + } + + $subscribers = (array) $subscriber; + + foreach ($subscribers as $subscriber) { + if (is_string($subscriber)) { + $subscriber = $this->app->make($subscriber); + } + + if (method_exists($subscriber, 'subscribe')) { + // 手动订阅 + $subscriber->subscribe($this); + } else { + // 智能订阅 + $this->observe($subscriber); + } + } + + return $this; + } + + /** + * 自动注册事件观察者 + * @access public + * @param string|object $observer 观察者 + * @param null|string $prefix 事件名前缀 + * @return $this + */ + public function observe($observer, string $prefix = '') + { + if (!$this->withEvent) { + return $this; + } + + if (is_string($observer)) { + $observer = $this->app->make($observer); + } + + $reflect = new ReflectionClass($observer); + $methods = $reflect->getMethods(ReflectionMethod::IS_PUBLIC); + + if (empty($prefix) && $reflect->hasProperty('eventPrefix')) { + $reflectProperty = $reflect->getProperty('eventPrefix'); + $reflectProperty->setAccessible(true); + $prefix = $reflectProperty->getValue($observer); + } + + foreach ($methods as $method) { + $name = $method->getName(); + if (0 === strpos($name, 'on')) { + $this->listen($prefix . substr($name, 2), [$observer, $name]); + } + } + + return $this; + } + + /** + * 触发事件 + * @access public + * @param string|object $event 事件名称 + * @param mixed $params 传入参数 + * @param bool $once 只获取一个有效返回值 + * @return mixed + */ + public function trigger($event, $params = null, bool $once = false) + { + if (!$this->withEvent) { + return; + } + + if (is_object($event)) { + $params = $event; + $event = get_class($event); + } + + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + $result = []; + $listeners = $this->listener[$event] ?? []; + $listeners = array_unique($listeners, SORT_REGULAR); + + foreach ($listeners as $key => $listener) { + $result[$key] = $this->dispatch($listener, $params); + + if (false === $result[$key] || (!is_null($result[$key]) && $once)) { + break; + } + } + + return $once ? end($result) : $result; + } + + /** + * 触发事件(只获取一个有效返回值) + * @param $event + * @param null $params + * @return mixed + */ + public function until($event, $params = null) + { + return $this->trigger($event, $params, true); + } + + /** + * 执行事件调度 + * @access protected + * @param mixed $event 事件方法 + * @param mixed $params 参数 + * @return mixed + */ + protected function dispatch($event, $params = null) + { + if (!is_string($event)) { + $call = $event; + } elseif (strpos($event, '::')) { + $call = $event; + } else { + $obj = $this->app->make($event); + $call = [$obj, 'handle']; + } + + return $this->app->invoke($call, [$params]); + } + +} diff --git a/vendor/topthink/framework/src/think/Exception.php b/vendor/topthink/framework/src/think/Exception.php new file mode 100644 index 0000000..468e29d --- /dev/null +++ b/vendor/topthink/framework/src/think/Exception.php @@ -0,0 +1,60 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 异常基础类 + * @package think + */ +class Exception extends \Exception +{ + /** + * 保存异常页面显示的额外Debug数据 + * @var array + */ + protected $data = []; + + /** + * 设置异常额外的Debug数据 + * 数据将会显示为下面的格式 + * + * Exception Data + * -------------------------------------------------- + * Label 1 + * key1 value1 + * key2 value2 + * Label 2 + * key1 value1 + * key2 value2 + * + * @access protected + * @param string $label 数据分类,用于异常页面显示 + * @param array $data 需要显示的数据,必须为关联数组 + */ + final protected function setData(string $label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外Debug数据 + * 主要用于输出到异常页面便于调试 + * @access public + * @return array 由setData设置的Debug数据 + */ + final public function getData() + { + return $this->data; + } + +} diff --git a/vendor/topthink/framework/src/think/Facade.php b/vendor/topthink/framework/src/think/Facade.php new file mode 100644 index 0000000..228c160 --- /dev/null +++ b/vendor/topthink/framework/src/think/Facade.php @@ -0,0 +1,102 @@ + +// +---------------------------------------------------------------------- +namespace think; + +/** + * Facade管理类 + */ +class Facade +{ + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + /** + * 创建Facade实例 + * @static + * @access protected + * @param string $class 类名或标识 + * @param array $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + protected static function createFacade(string $class = '', array $args = [], bool $newInstance = false) + { + $class = $class ?: static::class; + + $facadeClass = static::getFacadeClass(); + + if ($facadeClass) { + $class = $facadeClass; + } + + if (static::$alwaysNewInstance) { + $newInstance = true; + } + + + return Container::getInstance()->make($class, $args, $newInstance); + } + + /** + * 获取当前Facade对应类名 + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 带参数实例化当前Facade类 + * @access public + * @return object + */ + public static function instance(...$args) + { + if (__CLASS__ != static::class) { + return self::createFacade('', $args); + } + } + + /** + * 调用类的实例 + * @access public + * @param string $class 类名或者标识 + * @param array|true $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public static function make(string $class, $args = [], $newInstance = false) + { + if (__CLASS__ != static::class) { + return self::__callStatic('make', func_get_args()); + } + + if (true === $args) { + // 总是创建新的实例化对象 + $newInstance = true; + $args = []; + } + + return self::createFacade($class, $args, $newInstance); + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + + + + return call_user_func_array([static::createFacade(), $method], $params); + } +} diff --git a/vendor/topthink/framework/src/think/File.php b/vendor/topthink/framework/src/think/File.php new file mode 100644 index 0000000..cb4cca0 --- /dev/null +++ b/vendor/topthink/framework/src/think/File.php @@ -0,0 +1,187 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use SplFileInfo; +use think\exception\FileException; + +/** + * 文件上传类 + * @package think + */ +class File extends SplFileInfo +{ + + /** + * 文件hash规则 + * @var array + */ + protected $hash = []; + + protected $hashName; + + public function __construct(string $path, bool $checkPath = true) + { + if ($checkPath && !is_file($path)) { + throw new FileException(sprintf('The file "%s" does not exist', $path)); + } + + parent::__construct($path); + } + + /** + * 获取文件的哈希散列值 + * @access public + * @param string $type + * @return string + */ + public function hash(string $type = 'sha1'): string + { + if (!isset($this->hash[$type])) { + $this->hash[$type] = hash_file($type, $this->getPathname()); + } + + return $this->hash[$type]; + } + + /** + * 获取文件的MD5值 + * @access public + * @return string + */ + public function md5(): string + { + return $this->hash('md5'); + } + + /** + * 获取文件的SHA1值 + * @access public + * @return string + */ + public function sha1(): string + { + return $this->hash('sha1'); + } + + /** + * 获取文件类型信息 + * @access public + * @return string + */ + public function getMime(): string + { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $this->getPathname()); + } + + /** + * 移动文件 + * @access public + * @param string $directory 保存路径 + * @param string|null $name 保存的文件名 + * @return File + */ + public function move(string $directory, string $name = null): File + { + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { + $error = $msg; + }); + $renamed = rename($this->getPathname(), (string) $target); + restore_error_handler(); + if (!$renamed) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod((string) $target, 0666 & ~umask()); + + return $target; + } + + /** + * 实例化一个新文件 + * @param string $directory + * @param null|string $name + * @return File + */ + protected function getTargetFile(string $directory, string $name = null): File + { + if (!is_dir($directory)) { + if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { + throw new FileException(sprintf('Unable to create the "%s" directory', $directory)); + } + } elseif (!is_writable($directory)) { + throw new FileException(sprintf('Unable to write in the "%s" directory', $directory)); + } + + $target = rtrim($directory, '/\\') . \DIRECTORY_SEPARATOR . (null === $name ? $this->getBasename() : $this->getName($name)); + + return new self($target, false); + } + + /** + * 获取文件名 + * @param string $name + * @return string + */ + protected function getName(string $name): string + { + $originalName = str_replace('\\', '/', $name); + $pos = strrpos($originalName, '/'); + $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1); + + return $originalName; + } + + /** + * 文件扩展名 + * @return string + */ + public function extension(): string + { + return $this->getExtension(); + } + + /** + * 自动生成文件名 + * @access protected + * @param string|\Closure $rule + * @return string + */ + public function hashName($rule = 'date'): string + { + if (!$this->hashName) { + if ($rule instanceof \Closure) { + $this->hashName = call_user_func_array($rule, [$this]); + } else { + switch (true) { + case in_array($rule, hash_algos()): + $hash = $this->hash($rule); + $this->hashName = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2); + break; + case is_callable($rule): + $this->hashName = call_user_func($rule); + break; + default: + $this->hashName = date('Ymd') . DIRECTORY_SEPARATOR . md5((string) microtime(true)); + break; + } + } + } + + return $this->hashName . '.' . $this->extension(); + } +} diff --git a/vendor/topthink/framework/src/think/Filesystem.php b/vendor/topthink/framework/src/think/Filesystem.php new file mode 100644 index 0000000..a443f74 --- /dev/null +++ b/vendor/topthink/framework/src/think/Filesystem.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use think\filesystem\Driver; +use think\filesystem\driver\Local; +use think\helper\Arr; + +/** + * Class Filesystem + * @package think + * @mixin Driver + * @mixin Local + */ +class Filesystem extends Manager +{ + protected $namespace = '\\think\\filesystem\\driver\\'; + + /** + * @param null|string $name + * @return Driver + */ + public function disk(string $name = null): Driver + { + return $this->driver($name); + } + + protected function resolveType(string $name) + { + return $this->getDiskConfig($name, 'type', 'local'); + } + + protected function resolveConfig(string $name) + { + return $this->getDiskConfig($name); + } + + /** + * 获取缓存配置 + * @access public + * @param null|string $name 名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (!is_null($name)) { + return $this->app->config->get('filesystem.' . $name, $default); + } + + return $this->app->config->get('filesystem'); + } + + /** + * 获取磁盘配置 + * @param string $disk + * @param null $name + * @param null $default + * @return array + */ + public function getDiskConfig($disk, $name = null, $default = null) + { + if ($config = $this->getConfig("disks.{$disk}")) { + return Arr::get($config, $name, $default); + } + + throw new InvalidArgumentException("Disk [$disk] not found."); + } + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->getConfig('default'); + } +} diff --git a/vendor/topthink/framework/src/think/Http.php b/vendor/topthink/framework/src/think/Http.php new file mode 100644 index 0000000..a78b8b4 --- /dev/null +++ b/vendor/topthink/framework/src/think/Http.php @@ -0,0 +1,285 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\event\HttpEnd; +use think\event\HttpRun; +use think\event\RouteLoaded; +use think\exception\Handle; +use Throwable; + +/** + * Web应用管理类 + * @package think + */ +class Http +{ + + /** + * @var App + */ + protected $app; + + /** + * 应用名称 + * @var string + */ + protected $name; + + /** + * 应用路径 + * @var string + */ + protected $path; + + /** + * 是否绑定应用 + * @var bool + */ + protected $isBind = false; + + public function __construct(App $app) + { + $this->app = $app; + + $this->routePath = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR; + } + + /** + * 设置应用名称 + * @access public + * @param string $name 应用名称 + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + return $this; + } + + /** + * 获取应用名称 + * @access public + * @return string + */ + public function getName(): string + { + return $this->name ?: ''; + } + + /** + * 设置应用目录 + * @access public + * @param string $path 应用目录 + * @return $this + */ + public function path(string $path) + { + if (substr($path, -1) != DIRECTORY_SEPARATOR) { + $path .= DIRECTORY_SEPARATOR; + } + + $this->path = $path; + return $this; + } + + /** + * 获取应用路径 + * @access public + * @return string + */ + public function getPath(): string + { + return $this->path ?: ''; + } + + /** + * 获取路由目录 + * @access public + * @return string + */ + public function getRoutePath(): string + { + return $this->routePath; + } + + /** + * 设置路由目录 + * @access public + * @param string $path 路由定义目录 + * @return string + */ + public function setRoutePath(string $path): void + { + $this->routePath = $path; + } + + /** + * 设置应用绑定 + * @access public + * @param bool $bind 是否绑定 + * @return $this + */ + public function setBind(bool $bind = true) + { + $this->isBind = $bind; + return $this; + } + + /** + * 是否绑定应用 + * @access public + * @return bool + */ + public function isBind(): bool + { + return $this->isBind; + } + + /** + * 执行应用程序 + * @access public + * @param Request|null $request + * @return Response + */ + public function run(Request $request = null): Response + { + //自动创建request对象 + $request = $request ?? $this->app->make('request', [], true); + $this->app->instance('request', $request); + + try { + $response = $this->runWithRequest($request); + } catch (Throwable $e) { + $this->reportException($e); + + $response = $this->renderException($request, $e); + } + + return $response; + } + + /** + * 初始化 + */ + protected function initialize() + { + if (!$this->app->initialized()) { + $this->app->initialize(); + } + } + + /** + * 执行应用程序 + * @param Request $request + * @return mixed + */ + protected function runWithRequest(Request $request) + { + $this->initialize(); + + // 加载全局中间件 + $this->loadMiddleware(); + + // 设置开启事件机制 + $this->app->event->withEvent($this->app->config->get('app.with_event', true)); + + // 监听HttpRun + $this->app->event->trigger(HttpRun::class); + + return $this->app->middleware->pipeline() + ->send($request) + ->then(function ($request) { + return $this->dispatchToRoute($request); + }); + } + + protected function dispatchToRoute($request) + { + $withRoute = $this->app->config->get('app.with_route', true) ? function () { + $this->loadRoutes(); + } : null; + + return $this->app->route->dispatch($request, $withRoute); + } + + /** + * 加载全局中间件 + */ + protected function loadMiddleware(): void + { + if (is_file($this->app->getBasePath() . 'middleware.php')) { + $this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php'); + } + } + + /** + * 加载路由 + * @access protected + * @return void + */ + protected function loadRoutes(): void + { + // 加载路由定义 + $routePath = $this->getRoutePath(); + + if (is_dir($routePath)) { + $files = glob($routePath . '*.php'); + foreach ($files as $file) { + include $file; + } + } + + $this->app->event->trigger(RouteLoaded::class); + } + + /** + * Report the exception to the exception handler. + * + * @param Throwable $e + * @return void + */ + protected function reportException(Throwable $e) + { + $this->app->make(Handle::class)->report($e); + } + + /** + * Render the exception to a response. + * + * @param Request $request + * @param Throwable $e + * @return Response + */ + protected function renderException($request, Throwable $e) + { + return $this->app->make(Handle::class)->render($request, $e); + } + + /** + * HttpEnd + * @param Response $response + * @return void + */ + public function end(Response $response): void + { + $this->app->event->trigger(HttpEnd::class, $response); + + //执行中间件 + $this->app->middleware->end($response); + + // 写入日志 + $this->app->log->save(); + } + +} diff --git a/vendor/topthink/framework/src/think/Lang.php b/vendor/topthink/framework/src/think/Lang.php new file mode 100644 index 0000000..aed43fd --- /dev/null +++ b/vendor/topthink/framework/src/think/Lang.php @@ -0,0 +1,277 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 多语言管理类 + * @package think + */ +class Lang +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + // 默认语言 + 'default_lang' => 'zh-cn', + // 允许的语言列表 + 'allow_lang_list' => [], + // 是否使用Cookie记录 + 'use_cookie' => true, + // 扩展语言包 + 'extend_list' => [], + // 多语言cookie变量 + 'cookie_var' => 'think_lang', + // 多语言自动侦测变量名 + 'detect_var' => 'lang', + // Accept-Language转义为对应语言包名称 + 'accept_language' => [ + 'zh-hans-cn' => 'zh-cn', + ], + // 是否支持语言分组 + 'allow_group' => false, + ]; + + /** + * 多语言信息 + * @var array + */ + private $lang = []; + + /** + * 当前语言 + * @var string + */ + private $range = 'zh-cn'; + + /** + * 构造方法 + * @access public + * @param array $config + */ + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + $this->range = $this->config['default_lang']; + } + + public static function __make(Config $config) + { + return new static($config->get('lang')); + } + + /** + * 设置当前语言 + * @access public + * @param string $lang 语言 + * @return void + */ + public function setLangSet(string $lang): void + { + $this->range = $lang; + } + + /** + * 获取当前语言 + * @access public + * @return string + */ + public function getLangSet(): string + { + return $this->range; + } + + /** + * 获取默认语言 + * @access public + * @return string + */ + public function defaultLangSet() + { + return $this->config['default_lang']; + } + + /** + * 加载语言定义(不区分大小写) + * @access public + * @param string|array $file 语言文件 + * @param string $range 语言作用域 + * @return array + */ + public function load($file, $range = ''): array + { + $range = $range ?: $this->range; + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; + } + + $lang = []; + + foreach ((array) $file as $_file) { + if (is_file($_file)) { + $result = $this->parse($_file); + $lang = array_change_key_case($result) + $lang; + } + } + + if (!empty($lang)) { + $this->lang[$range] = $lang + $this->lang[$range]; + } + + return $this->lang[$range]; + } + + /** + * 解析语言文件 + * @access protected + * @param string $file 语言文件名 + * @return array + */ + protected function parse(string $file): array + { + $type = pathinfo($file, PATHINFO_EXTENSION); + + switch ($type) { + case 'php': + $result = include $file; + break; + case 'yml': + case 'yaml': + if (function_exists('yaml_parse_file')) { + $result = yaml_parse_file($file); + } + break; + } + + return isset($result) && is_array($result) ? $result : []; + } + + /** + * 判断是否存在语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param string $range 语言作用域 + * @return bool + */ + public function has(string $name, string $range = ''): bool + { + $range = $range ?: $this->range; + + if ($this->config['allow_group'] && strpos($name, '.')) { + [$name1, $name2] = explode('.', $name, 2); + return isset($this->lang[$range][strtolower($name1)][$name2]); + } + + return isset($this->lang[$range][strtolower($name)]); + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 + * @return mixed + */ + public function get(string $name = null, array $vars = [], string $range = '') + { + $range = $range ?: $this->range; + + // 空参数返回所有定义 + if (is_null($name)) { + return $this->lang[$range] ?? []; + } + + if ($this->config['allow_group'] && strpos($name, '.')) { + [$name1, $name2] = explode('.', $name, 2); + + $value = $this->lang[$range][strtolower($name1)][$name2] ?? $name; + } else { + $value = $this->lang[$range][strtolower($name)] ?? $name; + } + + // 变量解析 + if (!empty($vars) && is_array($vars)) { + /** + * Notes: + * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0 + * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数 + */ + if (key($vars) === 0) { + // 数字索引解析 + array_unshift($vars, $value); + $value = call_user_func_array('sprintf', $vars); + } else { + // 关联索引解析 + $replace = array_keys($vars); + foreach ($replace as &$v) { + $v = "{:{$v}}"; + } + $value = str_replace($replace, $vars, $value); + } + } + + return $value; + } + + /** + * 自动侦测设置获取语言选择 + * @access public + * @param Request $request + * @return string + */ + public function detect(Request $request): string + { + // 自动侦测设置获取语言选择 + $langSet = ''; + + if ($request->get($this->config['detect_var'])) { + // url中设置了语言变量 + $langSet = strtolower($request->get($this->config['detect_var'])); + } elseif ($request->cookie($this->config['cookie_var'])) { + // Cookie中设置了语言变量 + $langSet = strtolower($request->cookie($this->config['cookie_var'])); + } elseif ($request->server('HTTP_ACCEPT_LANGUAGE')) { + // 自动侦测浏览器语言 + $match = preg_match('/^([a-z\d\-]+)/i', $request->server('HTTP_ACCEPT_LANGUAGE'), $matches); + if ($match) { + $langSet = strtolower($matches[1]); + if (isset($this->config['accept_language'][$langSet])) { + $langSet = $this->config['accept_language'][$langSet]; + } + } + } + + if (empty($this->config['allow_lang_list']) || in_array($langSet, $this->config['allow_lang_list'])) { + // 合法的语言 + $this->range = $langSet; + } + + return $this->range; + } + + /** + * 保存当前语言到Cookie + * @access public + * @param Cookie $cookie Cookie对象 + * @return void + */ + public function saveToCookie(Cookie $cookie) + { + if ($this->config['use_cookie']) { + $cookie->set($this->config['cookie_var'], $this->range); + } + } + +} diff --git a/vendor/topthink/framework/src/think/Log.php b/vendor/topthink/framework/src/think/Log.php new file mode 100644 index 0000000..e9031c7 --- /dev/null +++ b/vendor/topthink/framework/src/think/Log.php @@ -0,0 +1,342 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use Psr\Log\LoggerInterface; +use think\event\LogWrite; +use think\helper\Arr; +use think\log\Channel; +use think\log\ChannelSet; + +/** + * 日志管理类 + * @package think + * @mixin Channel + */ +class Log extends Manager implements LoggerInterface +{ + const EMERGENCY = 'emergency'; + const ALERT = 'alert'; + const CRITICAL = 'critical'; + const ERROR = 'error'; + const WARNING = 'warning'; + const NOTICE = 'notice'; + const INFO = 'info'; + const DEBUG = 'debug'; + const SQL = 'sql'; + + protected $namespace = '\\think\\log\\driver\\'; + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->getConfig('default'); + } + + /** + * 获取日志配置 + * @access public + * @param null|string $name 名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (!is_null($name)) { + return $this->app->config->get('log.' . $name, $default); + } + + return $this->app->config->get('log'); + } + + /** + * 获取渠道配置 + * @param string $channel + * @param null $name + * @param null $default + * @return array + */ + public function getChannelConfig($channel, $name = null, $default = null) + { + if ($config = $this->getConfig("channels.{$channel}")) { + return Arr::get($config, $name, $default); + } + + throw new InvalidArgumentException("Channel [$channel] not found."); + } + + /** + * driver()的别名 + * @param string|array $name 渠道名 + * @return Channel|ChannelSet + */ + public function channel($name = null) + { + if (is_array($name)) { + return new ChannelSet($this, $name); + } + + return $this->driver($name); + } + + protected function resolveType(string $name) + { + return $this->getChannelConfig($name, 'type', 'file'); + } + + public function createDriver(string $name) + { + $driver = parent::createDriver($name); + + $lazy = !$this->getChannelConfig($name, "realtime_write", false) && !$this->app->runningInConsole(); + $allow = array_merge($this->getConfig("level", []), $this->getChannelConfig($name, "level", [])); + + return new Channel($name, $driver, $allow, $lazy, $this->app->event); + } + + protected function resolveConfig(string $name) + { + return $this->getChannelConfig($name); + } + + /** + * 清空日志信息 + * @access public + * @param string|array $channel 日志通道名 + * @return $this + */ + public function clear($channel = '*') + { + if ('*' == $channel) { + $channel = array_keys($this->drivers); + } + + $this->channel($channel)->clear(); + + return $this; + } + + /** + * 关闭本次请求日志写入 + * @access public + * @param string|array $channel 日志通道名 + * @return $this + */ + public function close($channel = '*') + { + if ('*' == $channel) { + $channel = array_keys($this->drivers); + } + + $this->channel($channel)->close(); + + return $this; + } + + /** + * 获取日志信息 + * @access public + * @param string $channel 日志通道名 + * @return array + */ + public function getLog(string $channel = null): array + { + return $this->channel($channel)->getLog(); + } + + /** + * 保存日志信息 + * @access public + * @return bool + */ + public function save(): bool + { + /** @var Channel $channel */ + foreach ($this->drivers as $channel) { + $channel->save(); + } + + return true; + } + + /** + * 记录日志信息 + * @access public + * @param mixed $msg 日志信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @param bool $lazy + * @return $this + */ + public function record($msg, string $type = 'info', array $context = [], bool $lazy = true) + { + $channel = $this->getConfig('type_channel.' . $type); + + $this->channel($channel)->record($msg, $type, $context, $lazy); + + return $this; + } + + /** + * 实时写入日志信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @return $this + */ + public function write($msg, string $type = 'info', array $context = []) + { + return $this->record($msg, $type, $context, false); + } + + /** + * 注册日志写入事件监听 + * @param $listener + * @return Event + */ + public function listen($listener) + { + return $this->app->event->listen(LogWrite::class, $listener); + } + + /** + * 记录日志信息 + * @access public + * @param string $level 日志级别 + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function log($level, $message, array $context = []): void + { + $this->record($message, $level, $context); + } + + /** + * 记录emergency信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function emergency($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录警报信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function alert($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录紧急情况 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function critical($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录错误信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function error($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录warning信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function warning($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录notice信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function notice($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录一般信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function info($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录调试信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function debug($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录sql信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function sql($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + public function __call($method, $parameters) + { + $this->log($method, ...$parameters); + } +} diff --git a/vendor/topthink/framework/src/think/Manager.php b/vendor/topthink/framework/src/think/Manager.php new file mode 100644 index 0000000..6097dbd --- /dev/null +++ b/vendor/topthink/framework/src/think/Manager.php @@ -0,0 +1,178 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use think\helper\Str; + +abstract class Manager +{ + /** @var App */ + protected $app; + + /** + * 驱动 + * @var array + */ + protected $drivers = []; + + /** + * 驱动的命名空间 + * @var string + */ + protected $namespace = null; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 获取驱动实例 + * @param null|string $name + * @return mixed + */ + protected function driver(string $name = null) + { + $name = $name ?: $this->getDefaultDriver(); + + if (is_null($name)) { + throw new InvalidArgumentException(sprintf( + 'Unable to resolve NULL driver for [%s].', static::class + )); + } + + // dump($name); + + return $this->drivers[$name] = $this->getDriver($name); + } + + /** + * 获取驱动实例 + * @param string $name + * @return mixed + */ + protected function getDriver(string $name) + { + return $this->drivers[$name] ?? $this->createDriver($name); + } + + /** + * 获取驱动类型 + * @param string $name + * @return mixed + */ + protected function resolveType(string $name) + { + return $name; + } + + /** + * 获取驱动配置 + * @param string $name + * @return mixed + */ + protected function resolveConfig(string $name) + { + return $name; + } + + /** + * 获取驱动类 + * @param string $type + * @return string + */ + protected function resolveClass(string $type): string + { + if ($this->namespace || false !== strpos($type, '\\')) { + $class = false !== strpos($type, '\\') ? $type : $this->namespace . Str::studly($type); + + if (class_exists($class)) { + return $class; + } + } + + throw new InvalidArgumentException("Driver [$type] not supported."); + } + + /** + * 获取驱动参数 + * @param $name + * @return array + */ + protected function resolveParams($name): array + { + $config = $this->resolveConfig($name); + return [$config]; + } + + /** + * 创建驱动 + * + * @param string $name + * @return mixed + * + */ + protected function createDriver(string $name) + { + $type = $this->resolveType($name); + + $method = 'create' . Str::studly($type) . 'Driver'; + + $params = $this->resolveParams($name); + + if (method_exists($this, $method)) { + return $this->$method(...$params); + } + + $class = $this->resolveClass($type); + + return $this->app->invokeClass($class, $params); + } + + /** + * 移除一个驱动实例 + * + * @param array|string|null $name + * @return $this + */ + public function forgetDriver($name = null) + { + $name = $name ?? $this->getDefaultDriver(); + + foreach ((array) $name as $cacheName) { + if (isset($this->drivers[$cacheName])) { + unset($this->drivers[$cacheName]); + } + } + + return $this; + } + + /** + * 默认驱动 + * @return string|null + */ + abstract public function getDefaultDriver(); + + /** + * 动态调用 + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->driver()->$method(...$parameters); + } +} diff --git a/vendor/topthink/framework/src/think/Middleware.php b/vendor/topthink/framework/src/think/Middleware.php new file mode 100644 index 0000000..0a277a1 --- /dev/null +++ b/vendor/topthink/framework/src/think/Middleware.php @@ -0,0 +1,259 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use InvalidArgumentException; +use LogicException; +use think\exception\Handle; +use Throwable; + +/** + * 中间件管理类 + * @package think + */ +class Middleware +{ + /** + * 中间件执行队列 + * @var array + */ + protected $queue = []; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 导入中间件 + * @access public + * @param array $middlewares + * @param string $type 中间件类型 + * @return void + */ + public function import(array $middlewares = [], string $type = 'global'): void + { + foreach ($middlewares as $middleware) { + $this->add($middleware, $type); + } + } + + /** + * 注册中间件 + * @access public + * @param mixed $middleware + * @param string $type 中间件类型 + * @return void + */ + public function add($middleware, string $type = 'global'): void + { + $middleware = $this->buildMiddleware($middleware, $type); + + if ($middleware) { + $this->queue[$type][] = $middleware; + $this->queue[$type] = array_unique($this->queue[$type], SORT_REGULAR); + } + } + + /** + * 注册路由中间件 + * @access public + * @param mixed $middleware + * @return void + */ + public function route($middleware): void + { + $this->add($middleware, 'route'); + } + + /** + * 注册控制器中间件 + * @access public + * @param mixed $middleware + * @return void + */ + public function controller($middleware): void + { + $this->add($middleware, 'controller'); + } + + /** + * 注册中间件到开始位置 + * @access public + * @param mixed $middleware + * @param string $type 中间件类型 + */ + public function unshift($middleware, string $type = 'global') + { + $middleware = $this->buildMiddleware($middleware, $type); + + if (!empty($middleware)) { + if (!isset($this->queue[$type])) { + $this->queue[$type] = []; + } + + array_unshift($this->queue[$type], $middleware); + } + } + + /** + * 获取注册的中间件 + * @access public + * @param string $type 中间件类型 + * @return array + */ + public function all(string $type = 'global'): array + { + return $this->queue[$type] ?? []; + } + + /** + * 调度管道 + * @access public + * @param string $type 中间件类型 + * @return Pipeline + */ + public function pipeline(string $type = 'global') + { + return (new Pipeline()) + ->through(array_map(function ($middleware) { + return function ($request, $next) use ($middleware) { + [$call, $param] = $middleware; + if (is_array($call) && is_string($call[0])) { + $call = [$this->app->make($call[0]), $call[1]]; + } + $response = call_user_func($call, $request, $next, $param); + + if (!$response instanceof Response) { + throw new LogicException('The middleware must return Response instance'); + } + return $response; + }; + }, $this->sortMiddleware($this->queue[$type] ?? []))) + ->whenException([$this, 'handleException']); + } + + /** + * 结束调度 + * @param Response $response + */ + public function end(Response $response) + { + foreach ($this->queue as $queue) { + foreach ($queue as $middleware) { + [$call] = $middleware; + if (is_array($call) && is_string($call[0])) { + $instance = $this->app->make($call[0]); + if (method_exists($instance, 'end')) { + $instance->end($response); + } + } + } + } + } + + /** + * 异常处理 + * @param Request $passable + * @param Throwable $e + * @return Response + */ + public function handleException($passable, Throwable $e) + { + /** @var Handle $handler */ + $handler = $this->app->make(Handle::class); + + $handler->report($e); + + $response = $handler->render($passable, $e); + + return $response; + } + + /** + * 解析中间件 + * @access protected + * @param mixed $middleware + * @param string $type 中间件类型 + * @return array + */ + protected function buildMiddleware($middleware, string $type): array + { + if (is_array($middleware)) { + [$middleware, $param] = $middleware; + } + + if ($middleware instanceof Closure) { + return [$middleware, $param ?? null]; + } + + if (!is_string($middleware)) { + throw new InvalidArgumentException('The middleware is invalid'); + } + + //中间件别名检查 + $alias = $this->app->config->get('middleware.alias', []); + + if (isset($alias[$middleware])) { + $middleware = $alias[$middleware]; + } + + if (is_array($middleware)) { + $this->import($middleware, $type); + return []; + } + + return [[$middleware, 'handle'], $param ?? null]; + } + + /** + * 中间件排序 + * @param array $middlewares + * @return array + */ + protected function sortMiddleware(array $middlewares) + { + $priority = $this->app->config->get('middleware.priority', []); + uasort($middlewares, function ($a, $b) use ($priority) { + $aPriority = $this->getMiddlewarePriority($priority, $a); + $bPriority = $this->getMiddlewarePriority($priority, $b); + return $bPriority - $aPriority; + }); + + return $middlewares; + } + + /** + * 获取中间件优先级 + * @param $priority + * @param $middleware + * @return int + */ + protected function getMiddlewarePriority($priority, $middleware) + { + [$call] = $middleware; + if (is_array($call) && is_string($call[0])) { + $index = array_search($call[0], array_reverse($priority)); + return false === $index ? -1 : $index; + } + return -1; + } + +} diff --git a/vendor/topthink/framework/src/think/Pipeline.php b/vendor/topthink/framework/src/think/Pipeline.php new file mode 100644 index 0000000..24c5122 --- /dev/null +++ b/vendor/topthink/framework/src/think/Pipeline.php @@ -0,0 +1,106 @@ + +// +---------------------------------------------------------------------- +namespace think; + +use Closure; +use Exception; +use Throwable; + +class Pipeline +{ + protected $passable; + + protected $pipes = []; + + protected $exceptionHandler; + + /** + * 初始数据 + * @param $passable + * @return $this + */ + public function send($passable) + { + $this->passable = $passable; + return $this; + } + + /** + * 调用栈 + * @param $pipes + * @return $this + */ + public function through($pipes) + { + $this->pipes = is_array($pipes) ? $pipes : func_get_args(); + return $this; + } + + /** + * 执行 + * @param Closure $destination + * @return mixed + */ + public function then(Closure $destination) + { + $pipeline = array_reduce( + array_reverse($this->pipes), + $this->carry(), + function ($passable) use ($destination) { + try { + return $destination($passable); + } catch (Throwable | Exception $e) { + return $this->handleException($passable, $e); + } + }); + + return $pipeline($this->passable); + } + + /** + * 设置异常处理器 + * @param callable $handler + * @return $this + */ + public function whenException($handler) + { + $this->exceptionHandler = $handler; + return $this; + } + + protected function carry() + { + return function ($stack, $pipe) { + return function ($passable) use ($stack, $pipe) { + try { + return $pipe($passable, $stack); + } catch (Throwable | Exception $e) { + return $this->handleException($passable, $e); + } + }; + }; + } + + /** + * 异常处理 + * @param $passable + * @param $e + * @return mixed + */ + protected function handleException($passable, Throwable $e) + { + if ($this->exceptionHandler) { + return call_user_func($this->exceptionHandler, $passable, $e); + } + throw $e; + } + +} diff --git a/vendor/topthink/framework/src/think/Request.php b/vendor/topthink/framework/src/think/Request.php new file mode 100644 index 0000000..a15847f --- /dev/null +++ b/vendor/topthink/framework/src/think/Request.php @@ -0,0 +1,2134 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\file\UploadedFile; +use think\route\Rule; + +/** + * 请求管理类 + * @package think + */ +class Request +{ + /** + * 兼容PATH_INFO获取 + * @var array + */ + protected $pathinfoFetch = ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL']; + + /** + * PATHINFO变量名 用于兼容模式 + * @var string + */ + protected $varPathinfo = 's'; + + /** + * 请求类型 + * @var string + */ + protected $varMethod = '_method'; + + /** + * 表单ajax伪装变量 + * @var string + */ + protected $varAjax = '_ajax'; + + /** + * 表单pjax伪装变量 + * @var string + */ + protected $varPjax = '_pjax'; + + /** + * 域名根 + * @var string + */ + protected $rootDomain = ''; + + /** + * HTTPS代理标识 + * @var string + */ + protected $httpsAgentName = ''; + + /** + * 前端代理服务器IP + * @var array + */ + protected $proxyServerIp = []; + + /** + * 前端代理服务器真实IP头 + * @var array + */ + protected $proxyServerIpHeader = ['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP']; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 域名(含协议及端口) + * @var string + */ + protected $domain; + + /** + * HOST(含端口) + * @var string + */ + protected $host; + + /** + * 子域名 + * @var string + */ + protected $subDomain; + + /** + * 泛域名 + * @var string + */ + protected $panDomain; + + /** + * 当前URL地址 + * @var string + */ + protected $url; + + /** + * 基础URL + * @var string + */ + protected $baseUrl; + + /** + * 当前执行的文件 + * @var string + */ + protected $baseFile; + + /** + * 访问的ROOT地址 + * @var string + */ + protected $root; + + /** + * pathinfo + * @var string + */ + protected $pathinfo; + + /** + * pathinfo(不含后缀) + * @var string + */ + protected $path; + + /** + * 当前请求的IP地址 + * @var string + */ + protected $realIP; + + /** + * 当前控制器名 + * @var string + */ + protected $controller; + + /** + * 当前操作名 + * @var string + */ + protected $action; + + /** + * 当前请求参数 + * @var array + */ + protected $param = []; + + /** + * 当前GET参数 + * @var array + */ + protected $get = []; + + /** + * 当前POST参数 + * @var array + */ + protected $post = []; + + /** + * 当前REQUEST参数 + * @var array + */ + protected $request = []; + + /** + * 当前路由对象 + * @var Rule + */ + protected $rule; + + /** + * 当前ROUTE参数 + * @var array + */ + protected $route = []; + + /** + * 中间件传递的参数 + * @var array + */ + protected $middleware = []; + + /** + * 当前PUT参数 + * @var array + */ + protected $put; + + /** + * SESSION对象 + * @var Session + */ + protected $session; + + /** + * COOKIE数据 + * @var array + */ + protected $cookie = []; + + /** + * ENV对象 + * @var Env + */ + protected $env; + + /** + * 当前SERVER参数 + * @var array + */ + protected $server = []; + + /** + * 当前FILE参数 + * @var array + */ + protected $file = []; + + /** + * 当前HEADER参数 + * @var array + */ + protected $header = []; + + /** + * 资源类型定义 + * @var array + */ + protected $mimeType = [ + 'xml' => 'application/xml,text/xml,application/x-xml', + 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', + 'js' => 'text/javascript,application/javascript,application/x-javascript', + 'css' => 'text/css', + 'rss' => 'application/rss+xml', + 'yaml' => 'application/x-yaml,text/yaml', + 'atom' => 'application/atom+xml', + 'pdf' => 'application/pdf', + 'text' => 'text/plain', + 'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*', + 'csv' => 'text/csv', + 'html' => 'text/html,application/xhtml+xml,*/*', + ]; + + /** + * 当前请求内容 + * @var string + */ + protected $content; + + /** + * 全局过滤规则 + * @var array + */ + protected $filter; + + /** + * php://input内容 + * @var string + */ + // php://input + protected $input; + + /** + * 请求安全Key + * @var string + */ + protected $secureKey; + + /** + * 是否合并Param + * @var bool + */ + protected $mergeParam = false; + + /** + * 架构函数 + * @access public + */ + public function __construct() + { + // 保存 php://input + $this->input = file_get_contents('php://input'); + } + + public static function __make(App $app) + { + $request = new static(); + + if (function_exists('apache_request_headers') && $result = apache_request_headers()) { + $header = $result; + } else { + $header = []; + $server = $_SERVER; + foreach ($server as $key => $val) { + if (0 === strpos($key, 'HTTP_')) { + $key = str_replace('_', '-', strtolower(substr($key, 5))); + $header[$key] = $val; + } + } + if (isset($server['CONTENT_TYPE'])) { + $header['content-type'] = $server['CONTENT_TYPE']; + } + if (isset($server['CONTENT_LENGTH'])) { + $header['content-length'] = $server['CONTENT_LENGTH']; + } + } + + $request->header = array_change_key_case($header); + + $request->server = $_SERVER; + $request->env = $app->env; + $request->get = $_GET; + $request->post = $_POST ?: $request->getInputData($request->input); + $request->put = $request->getInputData($request->input); + $request->request = $_REQUEST; + $request->cookie = $_COOKIE; + $request->file = $_FILES ?? []; + + return $request; + } + + /** + * 设置当前包含协议的域名 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function setDomain(string $domain) + { + $this->domain = $domain; + return $this; + } + + /** + * 获取当前包含协议的域名 + * @access public + * @param bool $port 是否需要去除端口号 + * @return string + */ + public function domain(bool $port = false): string + { + return $this->scheme() . '://' . $this->host($port); + } + + /** + * 获取当前根域名 + * @access public + * @return string + */ + public function rootDomain(): string + { + $root = $this->rootDomain; + + if (!$root) { + $item = explode('.', $this->host()); + $count = count($item); + $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; + } + + return $root; + } + + /** + * 设置当前泛域名的值 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function setSubDomain(string $domain) + { + $this->subDomain = $domain; + return $this; + } + + /** + * 获取当前子域名 + * @access public + * @return string + */ + public function subDomain(): string + { + if (is_null($this->subDomain)) { + // 获取当前主域名 + $rootDomain = $this->rootDomain(); + + if ($rootDomain) { + $this->subDomain = rtrim(stristr($this->host(), $rootDomain, true), '.'); + } else { + $this->subDomain = ''; + } + } + + return $this->subDomain; + } + + /** + * 设置当前泛域名的值 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function setPanDomain(string $domain) + { + $this->panDomain = $domain; + return $this; + } + + /** + * 获取当前泛域名的值 + * @access public + * @return string + */ + public function panDomain(): string + { + return $this->panDomain ?: ''; + } + + /** + * 设置当前完整URL 包括QUERY_STRING + * @access public + * @param string $url URL地址 + * @return $this + */ + public function setUrl(string $url) + { + $this->url = $url; + return $this; + } + + /** + * 获取当前完整URL 包括QUERY_STRING + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function url(bool $complete = false): string + { + if ($this->url) { + $url = $this->url; + } elseif ($this->server('HTTP_X_REWRITE_URL')) { + $url = $this->server('HTTP_X_REWRITE_URL'); + } elseif ($this->server('REQUEST_URI')) { + $url = $this->server('REQUEST_URI'); + } elseif ($this->server('ORIG_PATH_INFO')) { + $url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : ''); + } elseif (isset($_SERVER['argv'][1])) { + $url = $_SERVER['argv'][1]; + } else { + $url = ''; + } + + return $complete ? $this->domain() . $url : $url; + } + + /** + * 设置当前URL 不含QUERY_STRING + * @access public + * @param string $url URL地址 + * @return $this + */ + public function setBaseUrl(string $url) + { + $this->baseUrl = $url; + return $this; + } + + /** + * 获取当前URL 不含QUERY_STRING + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function baseUrl(bool $complete = false): string + { + if (!$this->baseUrl) { + $str = $this->url(); + $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; + } + + return $complete ? $this->domain() . $this->baseUrl : $this->baseUrl; + } + + /** + * 获取当前执行的文件 SCRIPT_NAME + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function baseFile(bool $complete = false): string + { + if (!$this->baseFile) { + $url = ''; + if (!$this->isCli()) { + $script_name = basename($this->server('SCRIPT_FILENAME')); + if (basename($this->server('SCRIPT_NAME')) === $script_name) { + $url = $this->server('SCRIPT_NAME'); + } elseif (basename($this->server('PHP_SELF')) === $script_name) { + $url = $this->server('PHP_SELF'); + } elseif (basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) { + $url = $this->server('ORIG_SCRIPT_NAME'); + } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) { + $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name; + } elseif ($this->server('DOCUMENT_ROOT') && strpos($this->server('SCRIPT_FILENAME'), $this->server('DOCUMENT_ROOT')) === 0) { + $url = str_replace('\\', '/', str_replace($this->server('DOCUMENT_ROOT'), '', $this->server('SCRIPT_FILENAME'))); + } + } + $this->baseFile = $url; + } + + return $complete ? $this->domain() . $this->baseFile : $this->baseFile; + } + + /** + * 设置URL访问根地址 + * @access public + * @param string $url URL地址 + * @return $this + */ + public function setRoot(string $url) + { + $this->root = $url; + return $this; + } + + /** + * 获取URL访问根地址 + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function root(bool $complete = false): string + { + if (!$this->root) { + $file = $this->baseFile(); + if ($file && 0 !== strpos($this->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + $this->root = rtrim($file, '/'); + } + + return $complete ? $this->domain() . $this->root : $this->root; + } + + /** + * 获取URL访问根目录 + * @access public + * @return string + */ + public function rootUrl(): string + { + $base = $this->root(); + $root = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base; + + if ('' != $root) { + $root = '/' . ltrim($root, '/'); + } + + return $root; + } + + /** + * 设置当前请求的pathinfo + * @access public + * @param string $pathinfo + * @return $this + */ + public function setPathinfo(string $pathinfo) + { + $this->pathinfo = $pathinfo; + return $this; + } + + /** + * 获取当前请求URL的pathinfo信息(含URL后缀) + * @access public + * @return string + */ + public function pathinfo(): string + { + if (is_null($this->pathinfo)) { + + + if (isset($_GET[$this->varPathinfo])) { + // 判断URL里面是否有兼容模式参数 + $pathinfo = $_GET[$this->varPathinfo]; + + $pathinfo = str_replace('/index.php','',$pathinfo); + + unset($_GET[$this->varPathinfo]); + + unset($this->get[$this->varPathinfo]); + + } elseif ($this->server('PATH_INFO')) { + + $pathinfo = $this->server('PATH_INFO'); + + + } elseif (false !== strpos(PHP_SAPI, 'cli')) { + $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI'); + } + + // 分析PATHINFO信息 + if (!isset($pathinfo)) { + foreach ($this->pathinfoFetch as $type) { + if ($this->server($type)) { + $pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ? + substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type); + break; + } + } + } + + if (!empty($pathinfo)) { + unset($this->get[$pathinfo], $this->request[$pathinfo]); + } + + + $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/'); + } + + return $this->pathinfo; + } + + /** + * 当前URL的访问后缀 + * @access public + * @return string + */ + public function ext(): string + { + return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); + } + + /** + * 获取当前请求的时间 + * @access public + * @param bool $float 是否使用浮点类型 + * @return integer|float + */ + public function time(bool $float = false) + { + return $float ? $this->server('REQUEST_TIME_FLOAT') : $this->server('REQUEST_TIME'); + } + + /** + * 当前请求的资源类型 + * @access public + * @return string + */ + public function type(): string + { + $accept = $this->server('HTTP_ACCEPT'); + + if (empty($accept)) { + return ''; + } + + foreach ($this->mimeType as $key => $val) { + $array = explode(',', $val); + foreach ($array as $k => $v) { + if (stristr($accept, $v)) { + return $key; + } + } + } + + return ''; + } + + /** + * 设置资源类型 + * @access public + * @param string|array $type 资源类型名 + * @param string $val 资源类型 + * @return void + */ + public function mimeType($type, $val = ''): void + { + if (is_array($type)) { + $this->mimeType = array_merge($this->mimeType, $type); + } else { + $this->mimeType[$type] = $val; + } + } + + /** + * 设置请求类型 + * @access public + * @param string $method 请求类型 + * @return $this + */ + public function setMethod(string $method) + { + $this->method = strtoupper($method); + return $this; + } + + /** + * 当前的请求类型 + * @access public + * @param bool $origin 是否获取原始请求类型 + * @return string + */ + public function method(bool $origin = false): string + { + if ($origin) { + // 获取原始请求类型 + return $this->server('REQUEST_METHOD') ?: 'GET'; + } elseif (!$this->method) { + if (isset($this->post[$this->varMethod])) { + $method = strtolower($this->post[$this->varMethod]); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) { + $this->method = strtoupper($method); + $this->{$method} = $this->post; + } else { + $this->method = 'POST'; + } + unset($this->post[$this->varMethod]); + } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) { + $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')); + } else { + $this->method = $this->server('REQUEST_METHOD') ?: 'GET'; + } + } + + return $this->method; + } + + /** + * 是否为GET请求 + * @access public + * @return bool + */ + public function isGet(): bool + { + return $this->method() == 'GET'; + } + + /** + * 是否为POST请求 + * @access public + * @return bool + */ + public function isPost(): bool + { + return $this->method() == 'POST'; + } + + /** + * 是否为PUT请求 + * @access public + * @return bool + */ + public function isPut(): bool + { + return $this->method() == 'PUT'; + } + + /** + * 是否为DELTE请求 + * @access public + * @return bool + */ + public function isDelete(): bool + { + return $this->method() == 'DELETE'; + } + + /** + * 是否为HEAD请求 + * @access public + * @return bool + */ + public function isHead(): bool + { + return $this->method() == 'HEAD'; + } + + /** + * 是否为PATCH请求 + * @access public + * @return bool + */ + public function isPatch(): bool + { + return $this->method() == 'PATCH'; + } + + /** + * 是否为OPTIONS请求 + * @access public + * @return bool + */ + public function isOptions(): bool + { + return $this->method() == 'OPTIONS'; + } + + /** + * 是否为cli + * @access public + * @return bool + */ + public function isCli(): bool + { + return PHP_SAPI == 'cli'; + } + + /** + * 是否为cgi + * @access public + * @return bool + */ + public function isCgi(): bool + { + return strpos(PHP_SAPI, 'cgi') === 0; + } + + /** + * 获取当前请求的参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function param($name = '', $default = null, $filter = '') + { + if (empty($this->mergeParam)) { + $method = $this->method(true); + + // 自动获取请求变量 + switch ($method) { + case 'POST': + $vars = $this->post(false); + break; + case 'PUT': + case 'DELETE': + case 'PATCH': + $vars = $this->put(false); + break; + default: + $vars = []; + } + + // 当前请求参数和URL地址中的参数合并 + $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); + + $this->mergeParam = true; + } + + if (is_array($name)) { + return $this->only($name, $this->param, $filter); + } + + return $this->input($this->param, $name, $default, $filter); + } + + /** + * 设置路由变量 + * @access public + * @param Rule $rule 路由对象 + * @return $this + */ + public function setRule(Rule $rule) + { + $this->rule = $rule; + return $this; + } + + /** + * 获取当前路由对象 + * @access public + * @return Rule|null + */ + public function rule() + { + return $this->rule; + } + + /** + * 设置路由变量 + * @access public + * @param array $route 路由变量 + * @return $this + */ + public function setRoute(array $route) + { + $this->route = array_merge($this->route, $route); + return $this; + } + + /** + * 获取路由参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function route($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + return $this->only($name, $this->route, $filter); + } + + return $this->input($this->route, $name, $default, $filter); + } + + /** + * 获取GET参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function get($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + return $this->only($name, $this->get, $filter); + } + + return $this->input($this->get, $name, $default, $filter); + } + + /** + * 获取中间件传递的参数 + * @access public + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function middleware($name, $default = null) + { + return $this->middleware[$name] ?? $default; + } + + /** + * 获取POST参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function post($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + return $this->only($name, $this->post, $filter); + } + + return $this->input($this->post, $name, $default, $filter); + } + + /** + * 获取PUT参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function put($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + return $this->only($name, $this->put, $filter); + } + + return $this->input($this->put, $name, $default, $filter); + } + + protected function getInputData($content): array + { + if (false !== strpos($this->contentType(), 'json')) { + return (array) json_decode($content, true); + } elseif (strpos($content, '=')) { + parse_str($content, $data); + return $data; + } + + return []; + } + + /** + * 设置获取DELETE参数 + * @access public + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function delete($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 设置获取PATCH参数 + * @access public + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function patch($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 获取request变量 + * @access public + * @param string|array $name 数据名称 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function request($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + return $this->only($name, $this->request, $filter); + } + + return $this->input($this->request, $name, $default, $filter); + } + + /** + * 获取环境变量 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function env(string $name = '', string $default = null) + { + if (empty($name)) { + return $this->env->get(); + } else { + $name = strtoupper($name); + } + + return $this->env->get($name, $default); + } + + /** + * 获取session数据 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function session(string $name = '', $default = null) + { + if ('' === $name) { + return $this->session->all(); + } + return $this->session->get($name, $default); + } + + /** + * 获取cookie参数 + * @access public + * @param mixed $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function cookie(string $name = '', $default = null, $filter = '') + { + if (!empty($name)) { + $data = $this->getData($this->cookie, $name, $default); + } else { + $data = $this->cookie; + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + } else { + $this->filterValue($data, $name, $filter); + } + + return $data; + } + + /** + * 获取server参数 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function server(string $name = '', string $default = '') + { + if (empty($name)) { + return $this->server; + } else { + $name = strtoupper($name); + } + + return $this->server[$name] ?? $default; + } + + /** + * 获取上传的文件信息 + * @access public + * @param string $name 名称 + * @return null|array|UploadedFile + */ + public function file(string $name = '') + { + $files = $this->file; + if (!empty($files)) { + + if (strpos($name, '.')) { + [$name, $sub] = explode('.', $name); + } + + // 处理上传文件 + $array = $this->dealUploadFile($files, $name); + + if ('' === $name) { + // 获取全部文件 + return $array; + } elseif (isset($sub) && isset($array[$name][$sub])) { + return $array[$name][$sub]; + } elseif (isset($array[$name])) { + return $array[$name]; + } + } + } + + protected function dealUploadFile(array $files, string $name): array + { + $array = []; + foreach ($files as $key => $file) { + if (is_array($file['name'])) { + $item = []; + $keys = array_keys($file); + $count = count($file['name']); + + for ($i = 0; $i < $count; $i++) { + if ($file['error'][$i] > 0) { + if ($name == $key) { + $this->throwUploadFileError($file['error'][$i]); + } else { + continue; + } + } + + $temp['key'] = $key; + + foreach ($keys as $_key) { + $temp[$_key] = $file[$_key][$i]; + } + + $item[] = new UploadedFile($temp['tmp_name'], $temp['name'], $temp['type'], $temp['error']); + } + + $array[$key] = $item; + } else { + if ($file instanceof File) { + $array[$key] = $file; + } else { + if ($file['error'] > 0) { + if ($key == $name) { + $this->throwUploadFileError($file['error']); + } else { + continue; + } + } + + $array[$key] = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error']); + } + } + } + + return $array; + } + + protected function throwUploadFileError($error) + { + static $fileUploadErrors = [ + 1 => 'upload File size exceeds the maximum value', + 2 => 'upload File size exceeds the maximum value', + 3 => 'only the portion of file is uploaded', + 4 => 'no file to uploaded', + 6 => 'upload temp dir not found', + 7 => 'file write error', + ]; + + $msg = $fileUploadErrors[$error]; + throw new Exception($msg, $error); + } + + /** + * 设置或者获取当前的Header + * @access public + * @param string $name header名称 + * @param string $default 默认值 + * @return string|array + */ + public function header(string $name = '', string $default = null) + { + if ('' === $name) { + return $this->header; + } + + $name = str_replace('_', '-', strtolower($name)); + + return $this->header[$name] ?? $default; + } + + /** + * 获取变量 支持过滤和默认值 + * @access public + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 + * @return mixed + */ + public function input(array $data = [], $name = '', $default = null, $filter = '') + { + if (false === $name) { + // 获取原始数据 + return $data; + } + + $name = (string) $name; + if ('' != $name) { + // 解析name + if (strpos($name, '/')) { + [$name, $type] = explode('/', $name); + } + + $data = $this->getData($data, $name); + + if (is_null($data)) { + return $default; + } + + if (is_object($data)) { + return $data; + } + } + + $data = $this->filterData($data, $filter, $name, $default); + + if (isset($type) && $data !== $default) { + // 强制类型转换 + $this->typeCast($data, $type); + } + + return $data; + } + + protected function filterData($data, $filter, $name, $default) + { + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + } else { + $this->filterValue($data, $name, $filter); + } + + return $data; + } + + /** + * 强制类型转换 + * @access public + * @param mixed $data + * @param string $type + * @return mixed + */ + private function typeCast(&$data, string $type) + { + switch (strtolower($type)) { + // 数组 + case 'a': + $data = (array) $data; + break; + // 数字 + case 'd': + $data = (int) $data; + break; + // 浮点 + case 'f': + $data = (float) $data; + break; + // 布尔 + case 'b': + $data = (boolean) $data; + break; + // 字符串 + case 's': + if (is_scalar($data)) { + $data = (string) $data; + } else { + throw new \InvalidArgumentException('variable type error:' . gettype($data)); + } + break; + } + } + + /** + * 获取数据 + * @access public + * @param array $data 数据源 + * @param string $name 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + protected function getData(array $data, string $name, $default = null) + { + foreach (explode('.', $name) as $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + return $default; + } + } + + return $data; + } + + /** + * 设置或获取当前的过滤规则 + * @access public + * @param mixed $filter 过滤规则 + * @return mixed + */ + public function filter($filter = null) + { + if (is_null($filter)) { + return $this->filter; + } + + $this->filter = $filter; + + return $this; + } + + protected function getFilter($filter, $default): array + { + if (is_null($filter)) { + $filter = []; + } else { + $filter = $filter ?: $this->filter; + if (is_string($filter) && false === strpos($filter, '/')) { + $filter = explode(',', $filter); + } else { + $filter = (array) $filter; + } + } + + $filter[] = $default; + + return $filter; + } + + /** + * 递归过滤给定的值 + * @access public + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 + * @return mixed + */ + public function filterValue(&$value, $key, $filters) + { + $default = array_pop($filters); + + foreach ($filters as $filter) { + if (is_callable($filter)) { + // 调用函数或者方法过滤 + $value = call_user_func($filter, $value); + } elseif (is_scalar($value)) { + if (is_string($filter) && false !== strpos($filter, '/')) { + // 正则过滤 + if (!preg_match($filter, $value)) { + // 匹配不成功返回默认值 + $value = $default; + break; + } + } elseif (!empty($filter)) { + // filter函数不存在时, 则使用filter_var进行过滤 + // filter为非整形值时, 调用filter_id取得过滤id + $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter)); + if (false === $value) { + $value = $default; + break; + } + } + } + } + + return $value; + } + + /** + * 是否存在某个请求参数 + * @access public + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 + * @return bool + */ + public function has(string $name, string $type = 'param', bool $checkEmpty = false): bool + { + if (!in_array($type, ['param', 'get', 'post', 'put', 'patch', 'route', 'delete', 'cookie', 'session', 'env', 'request', 'server', 'header', 'file'])) { + return false; + } + + $param = empty($this->$type) ? $this->$type() : $this->$type; + + if (is_object($param)) { + return $param->has($name); + } + + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($param[$val])) { + $param = $param[$val]; + } else { + return false; + } + } + + return ($checkEmpty && '' === $param) ? false : true; + } + + /** + * 获取指定的参数 + * @access public + * @param array $name 变量名 + * @param mixed $data 数据或者变量类型 + * @param string|array $filter 过滤方法 + * @return array + */ + public function only(array $name, $data = 'param', $filter = ''): array + { + $data = is_array($data) ? $data : $this->$data(); + + $item = []; + foreach ($name as $key => $val) { + + if (is_int($key)) { + $default = null; + $key = $val; + if (!isset($data[$key])) { + continue; + } + } else { + $default = $val; + } + + $item[$key] = $this->filterData($data[$key] ?? $default, $filter, $key, $default); + } + + return $item; + } + + /** + * 排除指定参数获取 + * @access public + * @param array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function except(array $name, string $type = 'param'): array + { + $param = $this->$type(); + + foreach ($name as $key) { + if (isset($param[$key])) { + unset($param[$key]); + } + } + + return $param; + } + + /** + * 当前是否ssl + * @access public + * @return bool + */ + public function isSsl(): bool + { + if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) { + return true; + } elseif ('https' == $this->server('REQUEST_SCHEME')) { + return true; + } elseif ('443' == $this->server('SERVER_PORT')) { + return true; + } elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) { + return true; + } elseif ($this->httpsAgentName && $this->server($this->httpsAgentName)) { + return true; + } + + return false; + } + + /** + * 当前是否JSON请求 + * @access public + * @return bool + */ + public function isJson(): bool + { + $acceptType = $this->type(); + + return false !== strpos($acceptType, 'json'); + } + + /** + * 当前是否Ajax请求 + * @access public + * @param bool $ajax true 获取原始ajax请求 + * @return bool + */ + public function isAjax(bool $ajax = false): bool + { + $value = $this->server('HTTP_X_REQUESTED_WITH'); + $result = $value && 'xmlhttprequest' == strtolower($value) ? true : false; + + if (true === $ajax) { + return $result; + } + + return $this->param($this->varAjax) ? true : $result; + } + + /** + * 当前是否Pjax请求 + * @access public + * @param bool $pjax true 获取原始pjax请求 + * @return bool + */ + public function isPjax(bool $pjax = false): bool + { + $result = !empty($this->server('HTTP_X_PJAX')) ? true : false; + + if (true === $pjax) { + return $result; + } + + return $this->param($this->varPjax) ? true : $result; + } + + /** + * 获取客户端IP地址 + * @access public + * @return string + */ + public function ip(): string + { + if (!empty($this->realIP)) { + return $this->realIP; + } + + $this->realIP = $this->server('REMOTE_ADDR', ''); + + // 如果指定了前端代理服务器IP以及其会发送的IP头 + // 则尝试获取前端代理服务器发送过来的真实IP + $proxyIp = $this->proxyServerIp; + $proxyIpHeader = $this->proxyServerIpHeader; + + if (count($proxyIp) > 0 && count($proxyIpHeader) > 0) { + // 从指定的HTTP头中依次尝试获取IP地址 + // 直到获取到一个合法的IP地址 + foreach ($proxyIpHeader as $header) { + $tempIP = $this->server($header); + + if (empty($tempIP)) { + continue; + } + + $tempIP = trim(explode(',', $tempIP)[0]); + + if (!$this->isValidIP($tempIP)) { + $tempIP = null; + } else { + break; + } + } + + // tempIP不为空,说明获取到了一个IP地址 + // 这时我们检查 REMOTE_ADDR 是不是指定的前端代理服务器之一 + // 如果是的话说明该 IP头 是由前端代理服务器设置的 + // 否则则是伪装的 + if (!empty($tempIP)) { + $realIPBin = $this->ip2bin($this->realIP); + + foreach ($proxyIp as $ip) { + $serverIPElements = explode('/', $ip); + $serverIP = $serverIPElements[0]; + $serverIPPrefix = $serverIPElements[1] ?? 128; + $serverIPBin = $this->ip2bin($serverIP); + + // IP类型不符 + if (strlen($realIPBin) !== strlen($serverIPBin)) { + continue; + } + + if (strncmp($realIPBin, $serverIPBin, (int) $serverIPPrefix) === 0) { + $this->realIP = $tempIP; + break; + } + } + } + } + + if (!$this->isValidIP($this->realIP)) { + $this->realIP = '0.0.0.0'; + } + + return $this->realIP; + } + + /** + * 检测是否是合法的IP地址 + * + * @param string $ip IP地址 + * @param string $type IP地址类型 (ipv4, ipv6) + * + * @return boolean + */ + public function isValidIP(string $ip, string $type = ''): bool + { + switch (strtolower($type)) { + case 'ipv4': + $flag = FILTER_FLAG_IPV4; + break; + case 'ipv6': + $flag = FILTER_FLAG_IPV6; + break; + default: + $flag = null; + break; + } + + return boolval(filter_var($ip, FILTER_VALIDATE_IP, $flag)); + } + + /** + * 将IP地址转换为二进制字符串 + * + * @param string $ip + * + * @return string + */ + public function ip2bin(string $ip): string + { + if ($this->isValidIP($ip, 'ipv6')) { + $IPHex = str_split(bin2hex(inet_pton($ip)), 4); + foreach ($IPHex as $key => $value) { + $IPHex[$key] = intval($value, 16); + } + $IPBin = vsprintf('%016b%016b%016b%016b%016b%016b%016b%016b', $IPHex); + } else { + $IPHex = str_split(bin2hex(inet_pton($ip)), 2); + foreach ($IPHex as $key => $value) { + $IPHex[$key] = intval($value, 16); + } + $IPBin = vsprintf('%08b%08b%08b%08b', $IPHex); + } + + return $IPBin; + } + + /** + * 检测是否使用手机访问 + * @access public + * @return bool + */ + public function isMobile(): bool + { + if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), "wap")) { + return true; + } elseif ($this->server('HTTP_ACCEPT') && strpos(strtoupper($this->server('HTTP_ACCEPT')), "VND.WAP.WML")) { + return true; + } elseif ($this->server('HTTP_X_WAP_PROFILE') || $this->server('HTTP_PROFILE')) { + return true; + } elseif ($this->server('HTTP_USER_AGENT') && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $this->server('HTTP_USER_AGENT'))) { + return true; + } + + return false; + } + + /** + * 当前URL地址中的scheme参数 + * @access public + * @return string + */ + public function scheme(): string + { + return $this->isSsl() ? 'https' : 'http'; + } + + /** + * 当前请求URL地址中的query参数 + * @access public + * @return string + */ + public function query(): string + { + return $this->server('QUERY_STRING', ''); + } + + /** + * 设置当前请求的host(包含端口) + * @access public + * @param string $host 主机名(含端口) + * @return $this + */ + public function setHost(string $host) + { + $this->host = $host; + + return $this; + } + + /** + * 当前请求的host + * @access public + * @param bool $strict true 仅仅获取HOST + * @return string + */ + public function host(bool $strict = false): string + { + if ($this->host) { + $host = $this->host; + } else { + $host = strval($this->server('HTTP_X_REAL_HOST') ?: $this->server('HTTP_HOST')); + } + + return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host; + } + + /** + * 当前请求URL地址中的port参数 + * @access public + * @return int + */ + public function port(): int + { + return (int) $this->server('SERVER_PORT', ''); + } + + /** + * 当前请求 SERVER_PROTOCOL + * @access public + * @return string + */ + public function protocol(): string + { + return $this->server('SERVER_PROTOCOL', ''); + } + + /** + * 当前请求 REMOTE_PORT + * @access public + * @return int + */ + public function remotePort(): int + { + return (int) $this->server('REMOTE_PORT', ''); + } + + /** + * 当前请求 HTTP_CONTENT_TYPE + * @access public + * @return string + */ + public function contentType(): string + { + $contentType = $this->header('Content-Type'); + + if ($contentType) { + if (strpos($contentType, ';')) { + [$type] = explode(';', $contentType); + } else { + $type = $contentType; + } + return trim($type); + } + + return ''; + } + + /** + * 获取当前请求的安全Key + * @access public + * @return string + */ + public function secureKey(): string + { + if (is_null($this->secureKey)) { + $this->secureKey = uniqid('', true); + } + + return $this->secureKey; + } + + /** + * 设置当前的控制器名 + * @access public + * @param string $controller 控制器名 + * @return $this + */ + public function setController(string $controller) + { + $this->controller = $controller; + return $this; + } + + /** + * 设置当前的操作名 + * @access public + * @param string $action 操作名 + * @return $this + */ + public function setAction(string $action) + { + $this->action = $action; + return $this; + } + + /** + * 获取当前的控制器名 + * @access public + * @param bool $convert 转换为小写 + * @return string + */ + public function controller(bool $convert = false): string + { + $name = $this->controller ?: ''; + return $convert ? strtolower($name) : $name; + } + + /** + * 获取当前的操作名 + * @access public + * @param bool $convert 转换为小写 + * @return string + */ + public function action(bool $convert = false): string + { + $name = $this->action ?: ''; + return $convert ? strtolower($name) : $name; + } + + /** + * 设置或者获取当前请求的content + * @access public + * @return string + */ + public function getContent(): string + { + if (is_null($this->content)) { + $this->content = $this->input; + } + + return $this->content; + } + + /** + * 获取当前请求的php://input + * @access public + * @return string + */ + public function getInput(): string + { + return $this->input; + } + + /** + * 生成请求令牌 + * @access public + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + public function buildToken(string $name = '__token__', $type = 'md5'): string + { + $type = is_callable($type) ? $type : 'md5'; + $token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT')); + + $this->session->set($name, $token); + + return $token; + } + + /** + * 检查请求令牌 + * @access public + * @param string $token 令牌名称 + * @param array $data 表单数据 + * @return bool + */ + public function checkToken(string $token = '__token__', array $data = []): bool + { + if (in_array($this->method(), ['GET', 'HEAD', 'OPTIONS'], true)) { + return true; + } + + if (!$this->session->has($token)) { + // 令牌数据无效 + return false; + } + + // Header验证 + if ($this->header('X-CSRF-TOKEN') && $this->session->get($token) === $this->header('X-CSRF-TOKEN')) { + // 防止重复提交 + $this->session->delete($token); // 验证完成销毁session + return true; + } + + if (empty($data)) { + $data = $this->post(); + } + + // 令牌验证 + if (isset($data[$token]) && $this->session->get($token) === $data[$token]) { + // 防止重复提交 + $this->session->delete($token); // 验证完成销毁session + return true; + } + + // 开启TOKEN重置 + $this->session->delete($token); + return false; + } + + /** + * 设置在中间件传递的数据 + * @access public + * @param array $middleware 数据 + * @return $this + */ + public function withMiddleware(array $middleware) + { + $this->middleware = array_merge($this->middleware, $middleware); + return $this; + } + + /** + * 设置GET数据 + * @access public + * @param array $get 数据 + * @return $this + */ + public function withGet(array $get) + { + $this->get = $get; + return $this; + } + + /** + * 设置POST数据 + * @access public + * @param array $post 数据 + * @return $this + */ + public function withPost(array $post) + { + $this->post = $post; + return $this; + } + + /** + * 设置COOKIE数据 + * @access public + * @param array $cookie 数据 + * @return $this + */ + public function withCookie(array $cookie) + { + $this->cookie = $cookie; + return $this; + } + + /** + * 设置SESSION数据 + * @access public + * @param Session $session 数据 + * @return $this + */ + public function withSession(Session $session) + { + $this->session = $session; + return $this; + } + + /** + * 设置SERVER数据 + * @access public + * @param array $server 数据 + * @return $this + */ + public function withServer(array $server) + { + $this->server = array_change_key_case($server, CASE_UPPER); + return $this; + } + + /** + * 设置HEADER数据 + * @access public + * @param array $header 数据 + * @return $this + */ + public function withHeader(array $header) + { + $this->header = array_change_key_case($header); + return $this; + } + + /** + * 设置ENV数据 + * @access public + * @param Env $env 数据 + * @return $this + */ + public function withEnv(Env $env) + { + $this->env = $env; + return $this; + } + + /** + * 设置php://input数据 + * @access public + * @param string $input RAW数据 + * @return $this + */ + public function withInput(string $input) + { + $this->input = $input; + if (!empty($input)) { + $this->post = $this->getInputData($input); + $this->put = $this->getInputData($input); + } + return $this; + } + + /** + * 设置文件上传数据 + * @access public + * @param array $files 上传信息 + * @return $this + */ + public function withFiles(array $files) + { + $this->file = $files; + return $this; + } + + /** + * 设置ROUTE变量 + * @access public + * @param array $route 数据 + * @return $this + */ + public function withRoute(array $route) + { + $this->route = $route; + return $this; + } + + /** + * 设置中间传递数据 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set(string $name, $value) + { + $this->middleware[$name] = $value; + } + + /** + * 获取中间传递数据的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get(string $name) + { + return $this->middleware($name); + } + + /** + * 检测中间传递数据的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset(string $name): bool + { + return isset($this->middleware[$name]); + } +} diff --git a/vendor/topthink/framework/src/think/Response.php b/vendor/topthink/framework/src/think/Response.php new file mode 100644 index 0000000..556696a --- /dev/null +++ b/vendor/topthink/framework/src/think/Response.php @@ -0,0 +1,410 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 响应输出基础类 + * @package think + */ +abstract class Response +{ + /** + * 原始数据 + * @var mixed + */ + protected $data; + + /** + * 当前contentType + * @var string + */ + protected $contentType = 'text/html'; + + /** + * 字符集 + * @var string + */ + protected $charset = 'utf-8'; + + /** + * 状态码 + * @var integer + */ + protected $code = 200; + + /** + * 是否允许请求缓存 + * @var bool + */ + protected $allowCache = true; + + /** + * 输出参数 + * @var array + */ + protected $options = []; + + /** + * header参数 + * @var array + */ + protected $header = []; + + /** + * 输出内容 + * @var string + */ + protected $content = null; + + /** + * Cookie对象 + * @var Cookie + */ + protected $cookie; + + /** + * Session对象 + * @var Session + */ + protected $session; + + /** + * 初始化 + * @access protected + * @param mixed $data 输出数据 + * @param int $code 状态码 + */ + protected function init($data = '', int $code = 200) + { + $this->data($data); + $this->code = $code; + + $this->contentType($this->contentType, $this->charset); + } + + /** + * 创建Response对象 + * @access public + * @param mixed $data 输出数据 + * @param string $type 输出类型 + * @param int $code 状态码 + * @return Response + */ + public static function create($data = '', string $type = 'html', int $code = 200): Response + { + $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type)); + + return Container::getInstance()->invokeClass($class, [$data, $code]); + } + + /** + * 设置Session对象 + * @access public + * @param Session $session Session对象 + * @return $this + */ + public function setSession(Session $session) + { + $this->session = $session; + return $this; + } + + /** + * 发送数据到客户端 + * @access public + * @return void + * @throws \InvalidArgumentException + */ + public function send(): void + { + // 处理输出数据 + $data = $this->getContent(); + + if (!headers_sent() && !empty($this->header)) { + // 发送状态码 + http_response_code($this->code); + // 发送头部信息 + foreach ($this->header as $name => $val) { + header($name . (!is_null($val) ? ':' . $val : '')); + } + } + if ($this->cookie) { + $this->cookie->save(); + } + + $this->sendData($data); + + if (function_exists('fastcgi_finish_request')) { + // 提高页面响应 + fastcgi_finish_request(); + } + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + return $data; + } + + /** + * 输出数据 + * @access protected + * @param string $data 要处理的数据 + * @return void + */ + protected function sendData(string $data): void + { + echo $data; + } + + /** + * 输出的参数 + * @access public + * @param mixed $options 输出参数 + * @return $this + */ + public function options(array $options = []) + { + $this->options = array_merge($this->options, $options); + + return $this; + } + + /** + * 输出数据设置 + * @access public + * @param mixed $data 输出数据 + * @return $this + */ + public function data($data) + { + $this->data = $data; + + return $this; + } + + /** + * 是否允许请求缓存 + * @access public + * @param bool $cache 允许请求缓存 + * @return $this + */ + public function allowCache(bool $cache) + { + $this->allowCache = $cache; + + return $this; + } + + /** + * 是否允许请求缓存 + * @access public + * @return $this + */ + public function isAllowCache() + { + return $this->allowCache; + } + + /** + * 设置Cookie + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param mixed $option 可选参数 + * @return $this + */ + public function cookie(string $name, string $value, $option = null) + { + $this->cookie->set($name, $value, $option); + + return $this; + } + + /** + * 设置响应头 + * @access public + * @param array $header 参数 + * @return $this + */ + public function header(array $header = []) + { + $this->header = array_merge($this->header, $header); + + return $this; + } + + /** + * 设置页面输出内容 + * @access public + * @param mixed $content + * @return $this + */ + public function content($content) + { + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + + return $this; + } + + /** + * 发送HTTP状态 + * @access public + * @param integer $code 状态码 + * @return $this + */ + public function code(int $code) + { + $this->code = $code; + + return $this; + } + + /** + * LastModified + * @access public + * @param string $time + * @return $this + */ + public function lastModified(string $time) + { + $this->header['Last-Modified'] = $time; + + return $this; + } + + /** + * Expires + * @access public + * @param string $time + * @return $this + */ + public function expires(string $time) + { + $this->header['Expires'] = $time; + + return $this; + } + + /** + * ETag + * @access public + * @param string $eTag + * @return $this + */ + public function eTag(string $eTag) + { + $this->header['ETag'] = $eTag; + + return $this; + } + + /** + * 页面缓存控制 + * @access public + * @param string $cache 状态码 + * @return $this + */ + public function cacheControl(string $cache) + { + $this->header['Cache-control'] = $cache; + + return $this; + } + + /** + * 页面输出类型 + * @access public + * @param string $contentType 输出类型 + * @param string $charset 输出编码 + * @return $this + */ + public function contentType(string $contentType, string $charset = 'utf-8') + { + $this->header['Content-Type'] = $contentType . '; charset=' . $charset; + + return $this; + } + + /** + * 获取头部信息 + * @access public + * @param string $name 头部名称 + * @return mixed + */ + public function getHeader(string $name = '') + { + if (!empty($name)) { + return $this->header[$name] ?? null; + } + + return $this->header; + } + + /** + * 获取原始数据 + * @access public + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * 获取输出数据 + * @access public + * @return string + */ + public function getContent(): string + { + if (null == $this->content) { + $content = $this->output($this->data); + + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + } + + return $this->content; + } + + /** + * 获取状态码 + * @access public + * @return integer + */ + public function getCode(): int + { + return $this->code; + } +} diff --git a/vendor/topthink/framework/src/think/Route.php b/vendor/topthink/framework/src/think/Route.php new file mode 100644 index 0000000..6bf7c80 --- /dev/null +++ b/vendor/topthink/framework/src/think/Route.php @@ -0,0 +1,894 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use think\exception\RouteNotFoundException; +use think\route\Dispatch; +use think\route\dispatch\Url as UrlDispatch; +use think\route\Domain; +use think\route\Resource; +use think\route\Rule; +use think\route\RuleGroup; +use think\route\RuleItem; +use think\route\RuleName; +use think\route\Url as UrlBuild; + +/** + * 路由管理类 + * @package think + */ +class Route +{ + /** + * REST定义 + * @var array + */ + protected $rest = [ + 'index' => ['get', '', 'index'], + 'create' => ['get', '/create', 'create'], + 'edit' => ['get', '//edit', 'edit'], + 'read' => ['get', '/', 'read'], + 'save' => ['post', '', 'save'], + 'update' => ['put', '/', 'update'], + 'delete' => ['delete', '/', 'delete'], + ]; + + /** + * 配置参数 + * @var array + */ + protected $config = [ + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 去除斜杠 + 'remove_slash' => false, + // 使用注解路由 + 'route_annotation' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '[\w\.]+', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // 访问控制器层名称 + 'controller_layer' => 'controller', + // 空控制器名 + 'empty_controller' => 'Error', + // 是否使用控制器后缀 + 'controller_suffix' => false, + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 操作方法后缀 + 'action_suffix' => '', + // 非路由变量是否使用普通参数方式(用于URL生成) + 'url_common_param' => true, + ]; + + /** + * 当前应用 + * @var App + */ + protected $app; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * @var RuleName + */ + protected $ruleName; + + /** + * 当前HOST + * @var string + */ + protected $host; + + /** + * 当前分组对象 + * @var RuleGroup + */ + protected $group; + + /** + * 路由绑定 + * @var array + */ + protected $bind = []; + + /** + * 域名对象 + * @var array + */ + protected $domains = []; + + /** + * 跨域路由规则 + * @var RuleGroup + */ + protected $cross; + + /** + * 路由是否延迟解析 + * @var bool + */ + protected $lazy = true; + + /** + * 路由是否测试模式 + * @var bool + */ + protected $isTest = false; + + /** + * (分组)路由规则是否合并解析 + * @var bool + */ + protected $mergeRuleRegex = false; + + /** + * 是否去除URL最后的斜线 + * @var bool + */ + protected $removeSlash = false; + + public function __construct(App $app) + { + $this->app = $app; + $this->ruleName = new RuleName(); + $this->setDefaultDomain(); + + if (is_file($this->app->getRuntimePath() . 'route.php')) { + // 读取路由映射文件 + $this->import(include $this->app->getRuntimePath() . 'route.php'); + } + } + + protected function init() + { + $this->config = array_merge($this->config, $this->app->config->get('route')); + + if (!empty($this->config['middleware'])) { + $this->app->middleware->import($this->config['middleware'], 'route'); + } + + $this->lazy($this->config['url_lazy_route']); + $this->mergeRuleRegex = $this->config['route_rule_merge']; + $this->removeSlash = $this->config['remove_slash']; + + $this->group->removeSlash($this->removeSlash); + } + + public function config(string $name = null) + { + if (is_null($name)) { + return $this->config; + } + + return $this->config[$name] ?? null; + } + + /** + * 设置路由域名及分组(包括资源路由)是否延迟解析 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy(bool $lazy = true) + { + $this->lazy = $lazy; + return $this; + } + + /** + * 设置路由为测试模式 + * @access public + * @param bool $test 路由是否测试模式 + * @return void + */ + public function setTestMode(bool $test): void + { + $this->isTest = $test; + } + + /** + * 检查路由是否为测试模式 + * @access public + * @return bool + */ + public function isTest(): bool + { + return $this->isTest; + } + + /** + * 设置路由域名及分组(包括资源路由)是否合并解析 + * @access public + * @param bool $merge 路由是否合并解析 + * @return $this + */ + public function mergeRuleRegex(bool $merge = true) + { + $this->mergeRuleRegex = $merge; + $this->group->mergeRuleRegex($merge); + + return $this; + } + + /** + * 初始化默认域名 + * @access protected + * @return void + */ + protected function setDefaultDomain(): void + { + // 注册默认域名 + $domain = new Domain($this); + + $this->domains['-'] = $domain; + + // 默认分组 + $this->group = $domain; + } + + /** + * 设置当前分组 + * @access public + * @param RuleGroup $group 域名 + * @return void + */ + public function setGroup(RuleGroup $group): void + { + $this->group = $group; + } + + /** + * 获取指定标识的路由分组 不指定则获取当前分组 + * @access public + * @param string $name 分组标识 + * @return RuleGroup + */ + public function getGroup(string $name = null) + { + return $name ? $this->ruleName->getGroup($name) : $this->group; + } + + /** + * 注册变量规则 + * @access public + * @param array $pattern 变量规则 + * @return $this + */ + public function pattern(array $pattern) + { + $this->group->pattern($pattern); + + return $this; + } + + /** + * 注册路由参数 + * @access public + * @param array $option 参数 + * @return $this + */ + public function option(array $option) + { + $this->group->option($option); + + return $this; + } + + /** + * 注册域名路由 + * @access public + * @param string|array $name 子域名 + * @param mixed $rule 路由规则 + * @return Domain + */ + public function domain($name, $rule = null): Domain + { + // 支持多个域名使用相同路由规则 + $domainName = is_array($name) ? array_shift($name) : $name; + + if (!isset($this->domains[$domainName])) { + $domain = (new Domain($this, $domainName, $rule)) + ->lazy($this->lazy) + ->removeSlash($this->removeSlash) + ->mergeRuleRegex($this->mergeRuleRegex); + + $this->domains[$domainName] = $domain; + } else { + $domain = $this->domains[$domainName]; + $domain->parseGroupRule($rule); + } + + if (is_array($name) && !empty($name)) { + foreach ($name as $item) { + $this->domains[$item] = $domainName; + } + } + + // 返回域名对象 + return $domain; + } + + /** + * 获取域名 + * @access public + * @return array + */ + public function getDomains(): array + { + return $this->domains; + } + + /** + * 获取RuleName对象 + * @access public + * @return RuleName + */ + public function getRuleName(): RuleName + { + return $this->ruleName; + } + + /** + * 设置路由绑定 + * @access public + * @param string $bind 绑定信息 + * @param string $domain 域名 + * @return $this + */ + public function bind(string $bind, string $domain = null) + { + $domain = is_null($domain) ? '-' : $domain; + + $this->bind[$domain] = $bind; + + return $this; + } + + /** + * 读取路由绑定信息 + * @access public + * @return array + */ + public function getBind(): array + { + return $this->bind; + } + + /** + * 读取路由绑定 + * @access public + * @param string $domain 域名 + * @return string|null + */ + public function getDomainBind(string $domain = null) + { + if (is_null($domain)) { + $domain = $this->host; + } elseif (false === strpos($domain, '.') && $this->request) { + $domain .= '.' . $this->request->rootDomain(); + } + + if ($this->request) { + $subDomain = $this->request->subDomain(); + + if (strpos($subDomain, '.')) { + $name = '*' . strstr($subDomain, '.'); + } + } + + if (isset($this->bind[$domain])) { + $result = $this->bind[$domain]; + } elseif (isset($name) && isset($this->bind[$name])) { + $result = $this->bind[$name]; + } elseif (!empty($subDomain) && isset($this->bind['*'])) { + $result = $this->bind['*']; + } else { + $result = null; + } + + return $result; + } + + /** + * 读取路由标识 + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 + * @param string $method 请求类型 + * @return RuleItem[] + */ + public function getName(string $name = null, string $domain = null, string $method = '*'): array + { + return $this->ruleName->getName($name, $domain, $method); + } + + /** + * 批量导入路由标识 + * @access public + * @param array $name 路由标识 + * @return $this + */ + public function import(array $name): void + { + $this->ruleName->import($name); + } + + /** + * 注册路由标识 + * @access public + * @param string $name 路由标识 + * @param RuleItem $ruleItem 路由规则 + * @param bool $first 是否优先 + * @return void + */ + public function setName(string $name, RuleItem $ruleItem, bool $first = false): void + { + $this->ruleName->setName($name, $ruleItem, $first); + } + + /** + * 保存路由规则 + * @access public + * @param string $rule 路由规则 + * @param RuleItem $ruleItem RuleItem对象 + * @return void + */ + public function setRule(string $rule, RuleItem $ruleItem = null): void + { + $this->ruleName->setRule($rule, $ruleItem); + } + + /** + * 读取路由 + * @access public + * @param string $rule 路由规则 + * @return RuleItem[] + */ + public function getRule(string $rule): array + { + return $this->ruleName->getRule($rule); + } + + /** + * 读取路由列表 + * @access public + * @return array + */ + public function getRuleList(): array + { + return $this->ruleName->getRuleList(); + } + + /** + * 清空路由规则 + * @access public + * @return void + */ + public function clear(): void + { + $this->ruleName->clear(); + + if ($this->group) { + $this->group->clear(); + } + } + + /** + * 注册路由规则 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $method 请求类型 + * @return RuleItem + */ + public function rule(string $rule, $route = null, string $method = '*'): RuleItem + { + return $this->group->addRule($rule, $route, $method); + } + + /** + * 设置跨域有效路由规则 + * @access public + * @param Rule $rule 路由规则 + * @param string $method 请求类型 + * @return $this + */ + public function setCrossDomainRule(Rule $rule, string $method = '*') + { + if (!isset($this->cross)) { + $this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex); + } + + $this->cross->addRuleItem($rule, $method); + + return $this; + } + + /** + * 注册路由分组 + * @access public + * @param string|\Closure $name 分组名称或者参数 + * @param mixed $route 分组路由 + * @return RuleGroup + */ + public function group($name, $route = null): RuleGroup + { + if ($name instanceof Closure) { + $route = $name; + $name = ''; + } + + return (new RuleGroup($this, $this->group, $name, $route)) + ->lazy($this->lazy) + ->removeSlash($this->removeSlash) + ->mergeRuleRegex($this->mergeRuleRegex); + } + + /** + * 注册路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function any(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, '*'); + } + + /** + * 注册GET路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function get(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'GET'); + } + + /** + * 注册POST路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function post(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'POST'); + } + + /** + * 注册PUT路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function put(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'PUT'); + } + + /** + * 注册DELETE路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function delete(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'DELETE'); + } + + /** + * 注册PATCH路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function patch(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'PATCH'); + } + + /** + * 注册OPTIONS路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function options(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'OPTIONS'); + } + + /** + * 注册资源路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @return Resource + */ + public function resource(string $rule, string $route): Resource + { + return (new Resource($this, $this->group, $rule, $route, $this->rest)) + ->lazy($this->lazy); + } + + /** + * 注册视图路由 + * @access public + * @param string $rule 路由规则 + * @param string $template 路由模板地址 + * @param array $vars 模板变量 + * @return RuleItem + */ + public function view(string $rule, string $template = '', array $vars = []): RuleItem + { + return $this->rule($rule, $template, 'GET')->view($vars); + } + + /** + * 注册重定向路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param int $status 状态码 + * @return RuleItem + */ + public function redirect(string $rule, string $route = '', int $status = 301): RuleItem + { + return $this->rule($rule, $route, '*')->redirect()->status($status); + } + + /** + * rest方法定义和修改 + * @access public + * @param string|array $name 方法名称 + * @param array|bool $resource 资源 + * @return $this + */ + public function rest($name, $resource = []) + { + if (is_array($name)) { + $this->rest = $resource ? $name : array_merge($this->rest, $name); + } else { + $this->rest[$name] = $resource; + } + + return $this; + } + + /** + * 获取rest方法定义的参数 + * @access public + * @param string $name 方法名称 + * @return array|null + */ + public function getRest(string $name = null) + { + if (is_null($name)) { + return $this->rest; + } + + return $this->rest[$name] ?? null; + } + + /** + * 注册未匹配路由规则后的处理 + * @access public + * @param string|Closure $route 路由地址 + * @param string $method 请求类型 + * @return RuleItem + */ + public function miss($route, string $method = '*'): RuleItem + { + return $this->group->miss($route, $method); + } + + /** + * 路由调度 + * @param Request $request + * @param Closure $withRoute + * @return Response + */ + public function dispatch(Request $request, $withRoute = null) + { + $this->request = $request; + $this->host = $this->request->host(true); + $this->init(); + + if ($withRoute) { + //加载路由 + $withRoute(); + $dispatch = $this->check(); + } else { + $dispatch = $this->url($this->path()); + } + + $dispatch->init($this->app); + + return $this->app->middleware->pipeline('route') + ->send($request) + ->then(function () use ($dispatch) { + return $dispatch->run(); + }); + } + + /** + * 检测URL路由 + * @access public + * @return Dispatch + * @throws RouteNotFoundException + */ + public function check(): Dispatch + { + // 自动检测域名路由 + $url = str_replace($this->config['pathinfo_depr'], '|', $this->path()); + + $completeMatch = $this->config['route_complete_match']; + + $result = $this->checkDomain()->check($this->request, $url, $completeMatch); + + if (false === $result && !empty($this->cross)) { + // 检测跨域路由 + $result = $this->cross->check($this->request, $url, $completeMatch); + } + + if (false !== $result) { + return $result; + } elseif ($this->config['url_route_must']) { + throw new RouteNotFoundException(); + } + + return $this->url($url); + } + + /** + * 获取当前请求URL的pathinfo信息(不含URL后缀) + * @access protected + * @return string + */ + protected function path(): string + { + $suffix = $this->config['url_html_suffix']; + $pathinfo = $this->request->pathinfo(); + + if (false === $suffix) { + // 禁止伪静态访问 + $path = $pathinfo; + } elseif ($suffix) { + // 去除正常的URL后缀 + $path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); + } else { + // 允许任何后缀访问 + $path = preg_replace('/\.' . $this->request->ext() . '$/i', '', $pathinfo); + } + + return $path; + } + + /** + * 默认URL解析 + * @access public + * @param string $url URL地址 + * @return Dispatch + */ + public function url(string $url): UrlDispatch + { + return new UrlDispatch($this->request, $this->group, $url); + } + + /** + * 检测域名的路由规则 + * @access protected + * @return Domain + */ + protected function checkDomain(): Domain + { + $item = false; + + if (count($this->domains) > 1) { + // 获取当前子域名 + $subDomain = $this->request->subDomain(); + + $domain = $subDomain ? explode('.', $subDomain) : []; + $domain2 = $domain ? array_pop($domain) : ''; + + if ($domain) { + // 存在三级域名 + $domain3 = array_pop($domain); + } + + if (isset($this->domains[$this->host])) { + // 子域名配置 + $item = $this->domains[$this->host]; + } elseif (isset($this->domains[$subDomain])) { + $item = $this->domains[$subDomain]; + } elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) { + // 泛三级域名 + $item = $this->domains['*.' . $domain2]; + $panDomain = $domain3; + } elseif (isset($this->domains['*']) && !empty($domain2)) { + // 泛二级域名 + if ('www' != $domain2) { + $item = $this->domains['*']; + $panDomain = $domain2; + } + } + + if (isset($panDomain)) { + // 保存当前泛域名 + $this->request->setPanDomain($panDomain); + } + } + + if (false === $item) { + // 检测全局域名规则 + $item = $this->domains['-']; + } + + if (is_string($item)) { + $item = $this->domains[$item]; + } + + return $item; + } + + /** + * URL生成 支持路由反射 + * @access public + * @param string $url 路由地址 + * @param array $vars 参数 ['a'=>'val1', 'b'=>'val2'] + * @return UrlBuild + */ + public function buildUrl(string $url = '', array $vars = []): UrlBuild + { + return $this->app->make(UrlBuild::class, [$this, $this->app, $url, $vars], true); + } + + /** + * 设置全局的路由分组参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return RuleGroup + */ + public function __call($method, $args) + { + return call_user_func_array([$this->group, $method], $args); + } +} diff --git a/vendor/topthink/framework/src/think/Service.php b/vendor/topthink/framework/src/think/Service.php new file mode 100644 index 0000000..68c6789 --- /dev/null +++ b/vendor/topthink/framework/src/think/Service.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use think\event\RouteLoaded; + +/** + * 系统服务基础类 + * @method void register() + * @method void boot() + */ +abstract class Service +{ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 加载路由 + * @access protected + * @param string $path 路由路径 + */ + protected function loadRoutesFrom($path) + { + $this->registerRoutes(function () use ($path) { + include $path; + }); + } + + /** + * 注册路由 + * @param Closure $closure + */ + protected function registerRoutes(Closure $closure) + { + $this->app->event->listen(RouteLoaded::class, $closure); + } + + /** + * 添加指令 + * @access protected + * @param array|string $commands 指令 + */ + protected function commands($commands) + { + $commands = is_array($commands) ? $commands : func_get_args(); + + Console::starting(function (Console $console) use ($commands) { + $console->addCommands($commands); + }); + } +} diff --git a/vendor/topthink/framework/src/think/Session.php b/vendor/topthink/framework/src/think/Session.php new file mode 100644 index 0000000..c344f0b --- /dev/null +++ b/vendor/topthink/framework/src/think/Session.php @@ -0,0 +1,65 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\helper\Arr; +use think\session\Store; + +/** + * Session管理类 + * @package think + * @mixin Store + */ +class Session extends Manager +{ + protected $namespace = '\\think\\session\\driver\\'; + + protected function createDriver(string $name) + { + $handler = parent::createDriver($name); + + return new Store($this->getConfig('name') ?: 'PHPSESSID', $handler, $this->getConfig('serialize')); + } + + /** + * 获取Session配置 + * @access public + * @param null|string $name 名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (!is_null($name)) { + return $this->app->config->get('session.' . $name, $default); + } + + return $this->app->config->get('session'); + } + + protected function resolveConfig(string $name) + { + $config = $this->app->config->get('session', []); + Arr::forget($config, 'type'); + return $config; + } + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->app->config->get('session.type', 'file'); + } +} diff --git a/vendor/topthink/framework/src/think/Validate.php b/vendor/topthink/framework/src/think/Validate.php new file mode 100644 index 0000000..a81c4d3 --- /dev/null +++ b/vendor/topthink/framework/src/think/Validate.php @@ -0,0 +1,1677 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use think\exception\ValidateException; +use think\helper\Str; +use think\validate\ValidateRule; + +/** + * 数据验证类 + * @package think + */ +class Validate +{ + /** + * 自定义验证类型 + * @var array + */ + protected $type = []; + + /** + * 验证类型别名 + * @var array + */ + protected $alias = [ + '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', + ]; + + /** + * 当前验证规则 + * @var array + */ + protected $rule = []; + + /** + * 验证提示信息 + * @var array + */ + protected $message = []; + + /** + * 验证字段描述 + * @var array + */ + protected $field = []; + + /** + * 默认规则提示 + * @var array + */ + protected $typeMsg = [ + 'require' => ':attribute require', + 'must' => ':attribute must', + 'number' => ':attribute must be numeric', + 'integer' => ':attribute must be integer', + 'float' => ':attribute must be float', + 'boolean' => ':attribute must be bool', + 'email' => ':attribute not a valid email address', + 'mobile' => ':attribute not a valid mobile', + 'array' => ':attribute must be a array', + 'accepted' => ':attribute must be yes,on or 1', + 'date' => ':attribute not a valid datetime', + 'file' => ':attribute not a valid file', + 'image' => ':attribute not a valid image', + 'alpha' => ':attribute must be alpha', + 'alphaNum' => ':attribute must be alpha-numeric', + 'alphaDash' => ':attribute must be alpha-numeric, dash, underscore', + 'activeUrl' => ':attribute not a valid domain or ip', + 'chs' => ':attribute must be chinese', + 'chsAlpha' => ':attribute must be chinese or alpha', + 'chsAlphaNum' => ':attribute must be chinese,alpha-numeric', + 'chsDash' => ':attribute must be chinese,alpha-numeric,underscore, dash', + 'url' => ':attribute not a valid url', + 'ip' => ':attribute not a valid ip', + 'dateFormat' => ':attribute must be dateFormat of :rule', + 'in' => ':attribute must be in :rule', + 'notIn' => ':attribute be notin :rule', + 'between' => ':attribute must between :1 - :2', + 'notBetween' => ':attribute not between :1 - :2', + 'length' => 'size of :attribute must be :rule', + 'max' => 'max size of :attribute must be :rule', + 'min' => 'min size of :attribute must be :rule', + 'after' => ':attribute cannot be less than :rule', + 'before' => ':attribute cannot exceed :rule', + 'expire' => ':attribute not within :rule', + 'allowIp' => 'access IP is not allowed', + 'denyIp' => 'access IP denied', + 'confirm' => ':attribute out of accord with :2', + 'different' => ':attribute cannot be same with :2', + 'egt' => ':attribute must greater than or equal :rule', + 'gt' => ':attribute must greater than :rule', + 'elt' => ':attribute must less than or equal :rule', + 'lt' => ':attribute must less than :rule', + 'eq' => ':attribute must equal :rule', + 'unique' => ':attribute has exists', + 'regex' => ':attribute not conform to the rules', + 'method' => 'invalid Request method', + 'token' => 'invalid token', + 'fileSize' => 'filesize not match', + 'fileExt' => 'extensions to upload is not allowed', + 'fileMime' => 'mimetype to upload is not allowed', + ]; + + /** + * 当前验证场景 + * @var string + */ + protected $currentScene; + + /** + * 内置正则验证规则 + * @var array + */ + protected $defaultRegex = [ + 'alpha' => '/^[A-Za-z]+$/', + 'alphaNum' => '/^[A-Za-z0-9]+$/', + 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/', + 'chs' => '/^[\x{4e00}-\x{9fa5}]+$/u', + 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', + 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u', + 'chsDash' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u', + 'mobile' => '/^1[3-9]\d{9}$/', + 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/', + 'zip' => '/\d{6}/', + ]; + + /** + * Filter_var 规则 + * @var array + */ + protected $filter = [ + 'email' => FILTER_VALIDATE_EMAIL, + 'ip' => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], + 'integer' => FILTER_VALIDATE_INT, + 'url' => FILTER_VALIDATE_URL, + 'macAddr' => FILTER_VALIDATE_MAC, + 'float' => FILTER_VALIDATE_FLOAT, + ]; + + /** + * 验证场景定义 + * @var array + */ + protected $scene = []; + + /** + * 验证失败错误信息 + * @var string|array + */ + protected $error = []; + + /** + * 是否批量验证 + * @var bool + */ + protected $batch = false; + + /** + * 验证失败是否抛出异常 + * @var bool + */ + protected $failException = false; + + /** + * 场景需要验证的规则 + * @var array + */ + protected $only = []; + + /** + * 场景需要移除的验证规则 + * @var array + */ + protected $remove = []; + + /** + * 场景需要追加的验证规则 + * @var array + */ + protected $append = []; + + /** + * 验证正则定义 + * @var array + */ + protected $regex = []; + + /** + * Db对象 + * @var Db + */ + protected $db; + + /** + * 语言对象 + * @var Lang + */ + protected $lang; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * @var Closure[] + */ + protected static $maker = []; + + /** + * 构造方法 + * @access public + */ + public function __construct() + { + if (!empty(static::$maker)) { + foreach (static::$maker as $maker) { + call_user_func($maker, $this); + } + } + } + + /** + * 设置服务注入 + * @access public + * @param Closure $maker + * @return void + */ + public static function maker(Closure $maker) + { + static::$maker[] = $maker; + } + + /** + * 设置Lang对象 + * @access public + * @param Lang $lang Lang对象 + * @return void + */ + public function setLang(Lang $lang) + { + $this->lang = $lang; + } + + /** + * 设置Db对象 + * @access public + * @param Db $db Db对象 + * @return void + */ + public function setDb(Db $db) + { + $this->db = $db; + } + + /** + * 设置Request对象 + * @access public + * @param Request $request Request对象 + * @return void + */ + public function setRequest(Request $request) + { + $this->request = $request; + } + + /** + * 添加字段验证规则 + * @access protected + * @param string|array $name 字段名称或者规则数组 + * @param mixed $rule 验证规则或者字段描述信息 + * @return $this + */ + public function rule($name, $rule = '') + { + if (is_array($name)) { + $this->rule = $name + $this->rule; + if (is_array($rule)) { + $this->field = array_merge($this->field, $rule); + } + } else { + $this->rule[$name] = $rule; + } + + return $this; + } + + /** + * 注册验证(类型)规则 + * @access public + * @param string $type 验证规则类型 + * @param callable $callback callback方法(或闭包) + * @param string $message 验证失败提示信息 + * @return $this + */ + public function extend(string $type, callable $callback = null, string $message = null) + { + $this->type[$type] = $callback; + + if ($message) { + $this->typeMsg[$type] = $message; + } + + return $this; + } + + /** + * 设置验证规则的默认提示信息 + * @access public + * @param string|array $type 验证规则类型名称或者数组 + * @param string $msg 验证提示信息 + * @return void + */ + public function setTypeMsg($type, string $msg = null): void + { + if (is_array($type)) { + $this->typeMsg = array_merge($this->typeMsg, $type); + } else { + $this->typeMsg[$type] = $msg; + } + } + + /** + * 设置提示信息 + * @access public + * @param array $message 错误信息 + * @return Validate + */ + public function message(array $message) + { + $this->message = array_merge($this->message, $message); + + return $this; + } + + /** + * 设置验证场景 + * @access public + * @param string $name 场景名 + * @return $this + */ + public function scene(string $name) + { + // 设置当前场景 + $this->currentScene = $name; + + return $this; + } + + /** + * 判断是否存在某个验证场景 + * @access public + * @param string $name 场景名 + * @return bool + */ + public function hasScene(string $name): bool + { + return isset($this->scene[$name]) || method_exists($this, 'scene' . $name); + } + + /** + * 设置批量验证 + * @access public + * @param bool $batch 是否批量验证 + * @return $this + */ + public function batch(bool $batch = true) + { + $this->batch = $batch; + + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access protected + * @param bool $fail 是否抛出异常 + * @return $this + */ + public function failException(bool $fail = true) + { + $this->failException = $fail; + + return $this; + } + + /** + * 指定需要验证的字段列表 + * @access public + * @param array $fields 字段名 + * @return $this + */ + public function only(array $fields) + { + $this->only = $fields; + + return $this; + } + + /** + * 移除某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 true 移除所有规则 + * @return $this + */ + public function remove($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + if (is_int($key)) { + $this->remove($rule); + } else { + $this->remove($key, $rule); + } + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->remove[$field] = $rule; + } + + return $this; + } + + /** + * 追加某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 + * @return $this + */ + public function append($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + $this->append($key, $rule); + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->append[$field] = $rule; + } + + return $this; + } + + /** + * 数据自动验证 + * @access public + * @param array $data 数据 + * @param array $rules 验证规则 + * @return bool + */ + public function check(array $data, array $rules = []): bool + { + $this->error = []; + + if (empty($rules)) { + // 读取验证规则 + $rules = $this->rule; + } + + if ($this->currentScene) { + $this->getScene($this->currentScene); + } + + foreach ($this->append as $key => $rule) { + if (!isset($rules[$key])) { + $rules[$key] = $rule; + } + } + + foreach ($rules as $key => $rule) { + // field => 'rule1|rule2...' field => ['rule1','rule2',...] + if (strpos($key, '|')) { + // 字段|描述 用于指定属性名称 + [$key, $title] = explode('|', $key); + } else { + $title = $this->field[$key] ?? $key; + } + + // 场景检测 + if (!empty($this->only) && !in_array($key, $this->only)) { + continue; + } + + // 获取数据 支持二维数组 + $value = $this->getDataValue($data, $key); + + // 字段验证 + if ($rule instanceof Closure) { + $result = call_user_func_array($rule, [$value, $data]); + } elseif ($rule instanceof ValidateRule) { + // 验证因子 + $result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg()); + } else { + $result = $this->checkItem($key, $value, $rule, $data, $title); + } + + if (true !== $result) { + // 没有返回true 则表示验证失败 + if (!empty($this->batch)) { + // 批量验证 + $this->error[$key] = $result; + } elseif ($this->failException) { + throw new ValidateException($result); + } else { + $this->error = $result; + return false; + } + } + } + + if (!empty($this->error)) { + if ($this->failException) { + throw new ValidateException($this->error); + } + return false; + } + + return true; + } + + /** + * 根据验证规则验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @return bool + */ + public function checkRule($value, $rules): bool + { + if ($rules instanceof Closure) { + return call_user_func_array($rules, [$value]); + } elseif ($rules instanceof ValidateRule) { + $rules = $rules->getRule(); + } elseif (is_string($rules)) { + $rules = explode('|', $rules); + } + + foreach ($rules as $key => $rule) { + if ($rule instanceof Closure) { + $result = call_user_func_array($rule, [$value]); + } else { + // 判断验证类型 + [$type, $rule] = $this->getValidateType($key, $rule); + + $callback = $this->type[$type] ?? [$this, $type]; + + $result = call_user_func_array($callback, [$value, $rule]); + } + + if (true !== $result) { + if ($this->failException) { + throw new ValidateException($result); + } + + return $result; + } + } + + return true; + } + + /** + * 验证单个字段规则 + * @access protected + * @param string $field 字段名 + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @param array $data 数据 + * @param string $title 字段描述 + * @param array $msg 提示信息 + * @return mixed + */ + protected function checkItem(string $field, $value, $rules, $data, string $title = '', array $msg = []) + { + if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) { + // 字段已经移除 无需验证 + return true; + } + + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] + if (is_string($rules)) { + $rules = explode('|', $rules); + } + + if (isset($this->append[$field])) { + // 追加额外的验证规则 + $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR); + } + + $i = 0; + foreach ($rules as $key => $rule) { + if ($rule instanceof Closure) { + $result = call_user_func_array($rule, [$value, $data]); + $info = is_numeric($key) ? '' : $key; + } else { + // 判断验证类型 + [$type, $rule, $info] = $this->getValidateType($key, $rule); + + if (isset($this->append[$field]) && in_array($info, $this->append[$field])) { + + } elseif (isset($this->remove[$field]) && in_array($info, $this->remove[$field])) { + // 规则已经移除 + $i++; + continue; + } + + if (isset($this->type[$type])) { + $result = call_user_func_array($this->type[$type], [$value, $rule, $data, $field, $title]); + } elseif ('must' == $info || 0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + $result = call_user_func_array([$this, $type], [$value, $rule, $data, $field, $title]); + } else { + $result = true; + } + } + + if (false === $result) { + // 验证失败 返回错误信息 + if (!empty($msg[$i])) { + $message = $msg[$i]; + if (is_string($message) && strpos($message, '{%') === 0) { + $message = $this->lang->get(substr($message, 2, -1)); + } + } else { + $message = $this->getRuleMsg($field, $title, $info, $rule); + } + + return $message; + } elseif (true !== $result) { + // 返回自定义错误信息 + if (is_string($result) && false !== strpos($result, ':')) { + $result = str_replace(':attribute', $title, $result); + + if (strpos($result, ':rule') && is_scalar($rule)) { + $result = str_replace(':rule', (string) $rule, $result); + } + } + + return $result; + } + $i++; + } + + return $result; + } + + /** + * 获取当前验证类型及规则 + * @access public + * @param mixed $key + * @param mixed $rule + * @return array + */ + protected function getValidateType($key, $rule): array + { + // 判断验证类型 + if (!is_numeric($key)) { + if (isset($this->alias[$key])) { + // 判断别名 + $key = $this->alias[$key]; + } + return [$key, $rule, $key]; + } + + if (strpos($rule, ':')) { + [$type, $rule] = explode(':', $rule, 2); + if (isset($this->alias[$type])) { + // 判断别名 + $type = $this->alias[$type]; + } + $info = $type; + } elseif (method_exists($this, $rule)) { + $type = $rule; + $info = $rule; + $rule = ''; + } else { + $type = 'is'; + $info = $rule; + } + + return [$type, $rule, $info]; + } + + /** + * 验证是否和某个字段的值一致 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @param string $field 字段名 + * @return bool + */ + public function confirm($value, $rule, array $data = [], string $field = ''): bool + { + if ('' == $rule) { + if (strpos($field, '_confirm')) { + $rule = strstr($field, '_confirm', true); + } else { + $rule = $field . '_confirm'; + } + } + + return $this->getDataValue($data, $rule) === $value; + } + + /** + * 验证是否和某个字段的值是否不同 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function different($value, $rule, array $data = []): bool + { + return $this->getDataValue($data, $rule) != $value; + } + + /** + * 验证是否大于等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function egt($value, $rule, array $data = []): bool + { + return $value >= $this->getDataValue($data, $rule); + } + + /** + * 验证是否大于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function gt($value, $rule, array $data = []): bool + { + return $value > $this->getDataValue($data, $rule); + } + + /** + * 验证是否小于等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function elt($value, $rule, array $data = []): bool + { + return $value <= $this->getDataValue($data, $rule); + } + + /** + * 验证是否小于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function lt($value, $rule, array $data = []): bool + { + return $value < $this->getDataValue($data, $rule); + } + + /** + * 验证是否等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function eq($value, $rule): bool + { + return $value == $rule; + } + + /** + * 必须验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function must($value, $rule = null): bool + { + return !empty($value) || '0' == $value; + } + + /** + * 验证字段值是否为有效格式 + * @access public + * @param mixed $value 字段值 + * @param string $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function is($value, string $rule, array $data = []): bool + { + switch (Str::camel($rule)) { + case 'require': + // 必须 + $result = !empty($value) || '0' == $value; + break; + case 'accepted': + // 接受 + $result = in_array($value, ['1', 'on', 'yes']); + break; + case 'date': + // 是否是一个有效日期 + $result = false !== strtotime($value); + break; + case 'activeUrl': + // 是否为有效的网址 + $result = checkdnsrr($value); + break; + case 'boolean': + case 'bool': + // 是否为布尔值 + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); + break; + case 'number': + $result = ctype_digit((string) $value); + break; + case 'alphaNum': + $result = ctype_alnum($value); + break; + case 'array': + // 是否为数组 + $result = is_array($value); + break; + case 'file': + $result = $value instanceof File; + break; + case 'image': + $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]); + break; + case 'token': + $result = $this->token($value, '__token__', $data); + break; + default: + if (isset($this->type[$rule])) { + // 注册的验证规则 + $result = call_user_func_array($this->type[$rule], [$value]); + } elseif (function_exists('ctype_' . $rule)) { + // ctype验证规则 + $ctypeFun = 'ctype_' . $rule; + $result = $ctypeFun($value); + } elseif (isset($this->filter[$rule])) { + // Filter_var验证规则 + $result = $this->filter($value, $this->filter[$rule]); + } else { + // 正则验证 + $result = $this->regex($value, $rule); + } + } + + return $result; + } + + // 判断图像类型 + protected function getImageType($image) + { + if (function_exists('exif_imagetype')) { + return exif_imagetype($image); + } + + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } + } + + /** + * 验证表单令牌 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function token($value, string $rule, array $data): bool + { + $rule = !empty($rule) ? $rule : '__token__'; + return $this->request->checkToken($rule, $data); + } + + /** + * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function activeUrl(string $value, string $rule = 'MX'): bool + { + if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { + $rule = 'MX'; + } + + return checkdnsrr($value, $rule); + } + + /** + * 验证是否有效IP + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 ipv4 ipv6 + * @return bool + */ + public function ip($value, string $rule = 'ipv4'): bool + { + if (!in_array($rule, ['ipv4', 'ipv6'])) { + $rule = 'ipv4'; + } + + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); + } + + /** + * 检测上传文件后缀 + * @access public + * @param File $file + * @param array|string $ext 允许后缀 + * @return bool + */ + protected function checkExt(File $file, $ext): bool + { + if (is_string($ext)) { + $ext = explode(',', $ext); + } + + return in_array(strtolower($file->extension()), $ext); + } + + /** + * 检测上传文件大小 + * @access public + * @param File $file + * @param integer $size 最大大小 + * @return bool + */ + protected function checkSize(File $file, $size): bool + { + return $file->getSize() <= (int) $size; + } + + /** + * 检测上传文件类型 + * @access public + * @param File $file + * @param array|string $mime 允许类型 + * @return bool + */ + protected function checkMime(File $file, $mime): bool + { + if (is_string($mime)) { + $mime = explode(',', $mime); + } + + return in_array(strtolower($file->getMime()), $mime); + } + + /** + * 验证上传文件后缀 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileExt($file, $rule): bool + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$this->checkExt($item, $rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $this->checkExt($file, $rule); + } + + return false; + } + + /** + * 验证上传文件类型 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileMime($file, $rule): bool + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$this->checkMime($item, $rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $this->checkMime($file, $rule); + } + + return false; + } + + /** + * 验证上传文件大小 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileSize($file, $rule): bool + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$this->checkSize($item, $rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $this->checkSize($file, $rule); + } + + return false; + } + + /** + * 验证图片的宽高及类型 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function image($file, $rule): bool + { + if (!($file instanceof File)) { + return false; + } + + if ($rule) { + $rule = explode(',', $rule); + + [$width, $height, $type] = getimagesize($file->getRealPath()); + + if (isset($rule[2])) { + $imageType = strtolower($rule[2]); + + if ('jpg' == $imageType) { + $imageType = 'jpeg'; + } + + if (image_type_to_extension($type, false) != $imageType) { + return false; + } + } + + [$w, $h] = $rule; + + return $w == $width && $h == $height; + } + + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); + } + + /** + * 验证时间和日期是否符合指定格式 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function dateFormat($value, $rule): bool + { + $info = date_parse_from_format($rule, $value); + return 0 == $info['warning_count'] && 0 == $info['error_count']; + } + + /** + * 验证是否唯一 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @param array $data 数据 + * @param string $field 验证字段名 + * @return bool + */ + public function unique($value, $rule, array $data = [], string $field = ''): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + + if (false !== strpos($rule[0], '\\')) { + // 指定模型类 + $db = new $rule[0]; + } else { + $db = $this->db->name($rule[0]); + } + + $key = $rule[1] ?? $field; + $map = []; + + if (strpos($key, '^')) { + // 支持多个字段验证 + $fields = explode('^', $key); + foreach ($fields as $key) { + if (isset($data[$key])) { + $map[] = [$key, '=', $data[$key]]; + } + } + } elseif (isset($data[$field])) { + $map[] = [$key, '=', $data[$field]]; + } else { + $map = []; + } + + $pk = !empty($rule[3]) ? $rule[3] : $db->getPk(); + + if (is_string($pk)) { + if (isset($rule[2])) { + $map[] = [$pk, '<>', $rule[2]]; + } elseif (isset($data[$pk])) { + $map[] = [$pk, '<>', $data[$pk]]; + } + } + + if ($db->where($map)->field($pk)->find()) { + return false; + } + + return true; + } + + /** + * 使用filter_var方式验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function filter($value, $rule): bool + { + if (is_string($rule) && strpos($rule, ',')) { + [$rule, $param] = explode(',', $rule); + } elseif (is_array($rule)) { + $param = $rule[1] ?? null; + $rule = $rule[0]; + } else { + $param = null; + } + + return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); + } + + /** + * 验证某个字段等于某个值的时候必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireIf($value, $rule, array $data = []): bool + { + [$field, $val] = explode(',', $rule); + + if ($this->getDataValue($data, $field) == $val) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 通过回调方法验证某个字段是否必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireCallback($value, $rule, array $data = []): bool + { + $result = call_user_func_array([$this, $rule], [$value, $data]); + + if ($result) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证某个字段有值的情况下必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireWith($value, $rule, array $data = []): bool + { + $val = $this->getDataValue($data, $rule); + + if (!empty($val)) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证某个字段没有值的情况下必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireWithout($value, $rule, array $data = []): bool + { + $val = $this->getDataValue($data, $rule); + + if (empty($val)) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证是否在范围内 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function in($value, $rule): bool + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证是否不在某个范围 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function notIn($value, $rule): bool + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * between验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function between($value, $rule): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + [$min, $max] = $rule; + + return $value >= $min && $value <= $max; + } + + /** + * 使用notbetween验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function notBetween($value, $rule): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + [$min, $max] = $rule; + + return $value < $min || $value > $max; + } + + /** + * 验证数据长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function length($value, $rule): bool + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + if (is_string($rule) && strpos($rule, ',')) { + // 长度区间 + [$min, $max] = explode(',', $rule); + return $length >= $min && $length <= $max; + } + + // 指定长度 + return $length == $rule; + } + + /** + * 验证数据最大长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function max($value, $rule): bool + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + return $length <= $rule; + } + + /** + * 验证数据最小长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function min($value, $rule): bool + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + return $length >= $rule; + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function after($value, $rule, array $data = []): bool + { + return strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function before($value, $rule, array $data = []): bool + { + return strtotime($value) <= strtotime($rule); + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function afterWith($value, $rule, array $data = []): bool + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function beforeWith($value, $rule, array $data = []): bool + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) <= strtotime($rule); + } + + /** + * 验证有效期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function expire($value, $rule): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + + [$start, $end] = $rule; + + if (!is_numeric($start)) { + $start = strtotime($start); + } + + if (!is_numeric($end)) { + $end = strtotime($end); + } + + return time() >= $start && time() <= $end; + } + + /** + * 验证IP许可 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function allowIp($value, $rule): bool + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证IP禁用 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function denyIp($value, $rule): bool + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 使用正则验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 正则规则或者预定义正则名 + * @return bool + */ + public function regex($value, $rule): bool + { + if (isset($this->regex[$rule])) { + $rule = $this->regex[$rule]; + } elseif (isset($this->defaultRegex[$rule])) { + $rule = $this->defaultRegex[$rule]; + } + + if (is_string($rule) && 0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { + // 不是正则表达式则两端补上/ + $rule = '/^' . $rule . '$/'; + } + + return is_scalar($value) && 1 === preg_match($rule, (string) $value); + } + + /** + * 获取错误信息 + * @return array|string + */ + public function getError() + { + return $this->error; + } + + /** + * 获取数据值 + * @access protected + * @param array $data 数据 + * @param string $key 数据标识 支持二维 + * @return mixed + */ + protected function getDataValue(array $data, $key) + { + if (is_numeric($key)) { + $value = $key; + } elseif (is_string($key) && strpos($key, '.')) { + // 支持多维数组验证 + foreach (explode('.', $key) as $key) { + if (!isset($data[$key])) { + $value = null; + break; + } + $value = $data = $data[$key]; + } + } else { + $value = $data[$key] ?? null; + } + + return $value; + } + + /** + * 获取验证规则的错误提示信息 + * @access protected + * @param string $attribute 字段英文名 + * @param string $title 字段描述名 + * @param string $type 验证规则名称 + * @param mixed $rule 验证规则数据 + * @return string|array + */ + protected function getRuleMsg(string $attribute, string $title, string $type, $rule) + { + if (isset($this->message[$attribute . '.' . $type])) { + $msg = $this->message[$attribute . '.' . $type]; + } elseif (isset($this->message[$attribute][$type])) { + $msg = $this->message[$attribute][$type]; + } elseif (isset($this->message[$attribute])) { + $msg = $this->message[$attribute]; + } elseif (isset($this->typeMsg[$type])) { + $msg = $this->typeMsg[$type]; + } elseif (0 === strpos($type, 'require')) { + $msg = $this->typeMsg['require']; + } else { + $msg = $title . $this->lang->get('not conform to the rules'); + } + + if (is_array($msg)) { + return $this->errorMsgIsArray($msg, $rule, $title); + } + + return $this->parseErrorMsg($msg, $rule, $title); + } + + /** + * 获取验证规则的错误提示信息 + * @access protected + * @param string $msg 错误信息 + * @param mixed $rule 验证规则数据 + * @param string $title 字段描述名 + * @return string + */ + protected function parseErrorMsg(string $msg, $rule, string $title) + { + if (0 === strpos($msg, '{%')) { + $msg = $this->lang->get(substr($msg, 2, -1)); + } elseif ($this->lang->has($msg)) { + $msg = $this->lang->get($msg); + } + + if (is_array($msg)) { + return $this->errorMsgIsArray($msg, $rule, $title); + } + + if (is_scalar($rule) && false !== strpos($msg, ':')) { + // 变量替换 + if (is_string($rule) && strpos($rule, ',')) { + $array = array_pad(explode(',', $rule), 3, ''); + } else { + $array = array_pad([], 3, ''); + } + + $msg = str_replace( + [':attribute', ':1', ':2', ':3'], + [$title, $array[0], $array[1], $array[2]], + $msg); + + if (strpos($msg, ':rule')) { + $msg = str_replace(':rule', (string) $rule, $msg); + } + } + + return $msg; + } + + /** + * 错误信息数组处理 + * @access protected + * @param array $msg 错误信息 + * @param mixed $rule 验证规则数据 + * @param string $title 字段描述名 + * @return array + */ + protected function errorMsgIsArray(array $msg, $rule, string $title) + { + foreach ($msg as $key => $val) { + if (is_string($val)) { + $msg[$key] = $this->parseErrorMsg($val, $rule, $title); + } + } + return $msg; + } + + /** + * 获取数据验证的场景 + * @access protected + * @param string $scene 验证场景 + * @return void + */ + protected function getScene(string $scene): void + { + $this->only = $this->append = $this->remove = []; + + if (method_exists($this, 'scene' . $scene)) { + call_user_func([$this, 'scene' . $scene]); + } elseif (isset($this->scene[$scene])) { + // 如果设置了验证适用场景 + $this->only = $this->scene[$scene]; + } + } + + /** + * 动态方法 直接调用is方法进行验证 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return bool + */ + public function __call($method, $args) + { + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_push($args, lcfirst($method)); + + return call_user_func_array([$this, 'is'], $args); + } +} diff --git a/vendor/topthink/framework/src/think/View.php b/vendor/topthink/framework/src/think/View.php new file mode 100644 index 0000000..c2e7368 --- /dev/null +++ b/vendor/topthink/framework/src/think/View.php @@ -0,0 +1,187 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\helper\Arr; + +/** + * 视图类 + * @package think + */ +class View extends Manager +{ + + protected $namespace = '\\think\\view\\driver\\'; + + /** + * 模板变量 + * @var array + */ + protected $data = []; + + /** + * 内容过滤 + * @var mixed + */ + protected $filter; + + /** + * 获取模板引擎 + * @access public + * @param string $type 模板引擎类型 + * @return $this + */ + public function engine(string $type = null) + { + return $this->driver($type); + } + + /** + * 模板变量赋值 + * @access public + * @param string|array $name 模板变量 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = null) + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + + return $this; + } + + /** + * 视图过滤 + * @access public + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + public function filter(callable $filter = null) + { + $this->filter = $filter; + return $this; + } + + /** + * 解析和获取模板内容 用于输出 + * @access public + * @param string $template 模板文件名或者内容 + * @param array $vars 模板变量 + * @return string + * @throws \Exception + */ + public function fetch(string $template = '', array $vars = []): string + { + return $this->getContent(function () use ($vars, $template) { + $this->engine()->fetch($template, array_merge($this->data, $vars)); + }); + } + + /** + * 渲染内容输出 + * @access public + * @param string $content 内容 + * @param array $vars 模板变量 + * @return string + */ + public function display(string $content, array $vars = []): string + { + return $this->getContent(function () use ($vars, $content) { + $this->engine()->display($content, array_merge($this->data, $vars)); + }); + } + + /** + * 获取模板引擎渲染内容 + * @param $callback + * @return string + * @throws \Exception + */ + protected function getContent($callback): string + { + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + + // 渲染输出 + try { + $callback(); + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } + + // 获取并清空缓存 + $content = ob_get_clean(); + + if ($this->filter) { + $content = call_user_func_array($this->filter, [$content]); + } + + return $content; + } + + /** + * 模板变量赋值 + * @access public + * @param string $name 变量名 + * @param mixed $value 变量值 + */ + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + /** + * 取得模板显示变量的值 + * @access protected + * @param string $name 模板变量 + * @return mixed + */ + public function __get($name) + { + return $this->data[$name]; + } + + /** + * 检测模板变量是否设置 + * @access public + * @param string $name 模板变量名 + * @return bool + */ + public function __isset($name) + { + return isset($this->data[$name]); + } + + protected function resolveConfig(string $name) + { + $config = $this->app->config->get('view', []); + Arr::forget($config, 'type'); + return $config; + } + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->app->config->get('view.type', 'php'); + } + +} diff --git a/vendor/topthink/framework/src/think/cache/Driver.php b/vendor/topthink/framework/src/think/cache/Driver.php new file mode 100644 index 0000000..f4198b4 --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/Driver.php @@ -0,0 +1,347 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache; + +use Closure; +use DateInterval; +use DateTime; +use DateTimeInterface; +use Exception; +use Psr\SimpleCache\CacheInterface; +use think\Container; +use think\contract\CacheHandlerInterface; +use think\exception\InvalidArgumentException; +use throwable; + +/** + * 缓存基础类 + */ +abstract class Driver implements CacheInterface, CacheHandlerInterface +{ + /** + * 驱动句柄 + * @var object + */ + protected $handler = null; + + /** + * 缓存读取次数 + * @var integer + */ + protected $readTimes = 0; + + /** + * 缓存写入次数 + * @var integer + */ + protected $writeTimes = 0; + + /** + * 缓存参数 + * @var array + */ + protected $options = []; + + /** + * 缓存标签 + * @var array + */ + protected $tag = []; + + /** + * 获取有效期 + * @access protected + * @param integer|DateTimeInterface|DateInterval $expire 有效期 + * @return int + */ + protected function getExpireTime($expire): int + { + if ($expire instanceof DateTimeInterface) { + $expire = $expire->getTimestamp() - time(); + } elseif ($expire instanceof DateInterval) { + $expire = DateTime::createFromFormat('U', (string) time()) + ->add($expire) + ->format('U') - time(); + } + + return (int) $expire; + } + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + public function getCacheKey(string $name): string + { + return $this->options['prefix'] . $name; + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function pull(string $name) + { + $result = $this->get($name, false); + + if ($result) { + $this->delete($name); + return $result; + } + } + + /** + * 追加(数组)缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @return void + */ + public function push(string $name, $value): void + { + $item = $this->get($name, []); + + if (!is_array($item)) { + throw new InvalidArgumentException('only array cache can be push'); + } + + $item[] = $value; + + if (count($item) > 1000) { + array_shift($item); + } + + $item = array_unique($item); + + $this->set($name, $item); + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember(string $name, $value, $expire = null) + { + if ($this->has($name)) { + return $this->get($name); + } + + $time = time(); + + while ($time + 5 > time() && $this->has($name . '_lock')) { + // 存在锁定则等待 + usleep(200000); + } + + try { + // 锁定 + $this->set($name . '_lock', true); + + if ($value instanceof Closure) { + // 获取缓存数据 + $value = Container::getInstance()->invokeFunction($value); + } + + // 缓存数据 + $this->set($name, $value, $expire); + + // 解锁 + $this->delete($name . '_lock'); + } catch (Exception | throwable $e) { + $this->delete($name . '_lock'); + throw $e; + } + + return $value; + } + + /** + * 缓存标签 + * @access public + * @param string|array $name 标签名 + * @return TagSet + */ + public function tag($name): TagSet + { + $name = (array) $name; + $key = implode('-', $name); + + if (!isset($this->tag[$key])) { + $name = array_map(function ($val) { + return $this->getTagKey($val); + }, $name); + $this->tag[$key] = new TagSet($name, $this); + } + + return $this->tag[$key]; + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 标签标识 + * @return array + */ + public function getTagItems(string $tag): array + { + return $this->get($tag, []); + } + + /** + * 获取实际标签名 + * @access public + * @param string $tag 标签名 + * @return string + */ + public function getTagKey(string $tag): string + { + return $this->options['tag_prefix'] . md5($tag); + } + + /** + * 序列化数据 + * @access protected + * @param mixed $data 缓存数据 + * @return string + */ + protected function serialize($data): string + { + if (is_numeric($data)) { + return (string) $data; + } + + $serialize = $this->options['serialize'][0] ?? "serialize"; + + return $serialize($data); + } + + /** + * 反序列化数据 + * @access protected + * @param string $data 缓存数据 + * @return mixed + */ + protected function unserialize(string $data) + { + if (is_numeric($data)) { + return $data; + } + + $unserialize = $this->options['serialize'][1] ?? "unserialize"; + + return $unserialize($data); + } + + /** + * 返回句柄对象,可执行其它高级方法 + * + * @access public + * @return object + */ + public function handler() + { + return $this->handler; + } + + /** + * 返回缓存读取次数 + * @access public + * @return int + */ + public function getReadTimes(): int + { + return $this->readTimes; + } + + /** + * 返回缓存写入次数 + * @access public + * @return int + */ + public function getWriteTimes(): int + { + return $this->writeTimes; + } + + /** + * 读取缓存 + * @access public + * @param iterable $keys 缓存变量名 + * @param mixed $default 默认值 + * @return iterable + * @throws InvalidArgumentException + */ + public function getMultiple($keys, $default = null): iterable + { + $result = []; + + foreach ($keys as $key) { + $result[$key] = $this->get($key, $default); + } + + return $result; + } + + /** + * 写入缓存 + * @access public + * @param iterable $values 缓存数据 + * @param null|int|\DateInterval $ttl 有效时间 0为永久 + * @return bool + */ + public function setMultiple($values, $ttl = null): bool + { + foreach ($values as $key => $val) { + $result = $this->set($key, $val, $ttl); + + if (false === $result) { + return false; + } + } + + return true; + } + + /** + * 删除缓存 + * @access public + * @param iterable $keys 缓存变量名 + * @return bool + * @throws InvalidArgumentException + */ + public function deleteMultiple($keys): bool + { + foreach ($keys as $key) { + $result = $this->delete($key); + + if (false === $result) { + return false; + } + } + + return true; + } + + public function __call($method, $args) + { + return call_user_func_array([$this->handler, $method], $args); + } +} diff --git a/vendor/topthink/framework/src/think/cache/TagSet.php b/vendor/topthink/framework/src/think/cache/TagSet.php new file mode 100644 index 0000000..d890c49 --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/TagSet.php @@ -0,0 +1,130 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache; + +/** + * 标签集合 + */ +class TagSet +{ + /** + * 标签的缓存Key + * @var array + */ + protected $tag; + + /** + * 缓存句柄 + * @var Driver + */ + protected $handler; + + /** + * 架构函数 + * @access public + * @param array $tag 缓存标签 + * @param Driver $cache 缓存对象 + */ + public function __construct(array $tag, Driver $cache) + { + $this->tag = $tag; + $this->handler = $cache; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set(string $name, $value, $expire = null): bool + { + $this->handler->set($name, $value, $expire); + + $this->append($name); + + return true; + } + + /** + * 追加缓存标识到标签 + * @access public + * @param string $name 缓存变量名 + * @return void + */ + public function append(string $name): void + { + $name = $this->handler->getCacheKey($name); + + foreach ($this->tag as $tag) { + $this->handler->push($tag, $name); + } + } + + /** + * 写入缓存 + * @access public + * @param iterable $values 缓存数据 + * @param null|int|\DateInterval $ttl 有效时间 0为永久 + * @return bool + */ + public function setMultiple($values, $ttl = null): bool + { + foreach ($values as $key => $val) { + $result = $this->set($key, $val, $ttl); + + if (false === $result) { + return false; + } + } + + return true; + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember(string $name, $value, $expire = null) + { + $result = $this->handler->remember($name, $value, $expire); + + $this->append($name); + + return $result; + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + // 指定标签清除 + foreach ($this->tag as $tag) { + $names = $this->handler->getTagItems($tag); + + $this->handler->clearTag($names); + $this->handler->delete($tag); + } + + return true; + } +} diff --git a/vendor/topthink/framework/src/think/cache/driver/File.php b/vendor/topthink/framework/src/think/cache/driver/File.php new file mode 100644 index 0000000..f0122f5 --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/File.php @@ -0,0 +1,304 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use FilesystemIterator; +use think\App; +use think\cache\Driver; + +/** + * 文件缓存类 + */ +class File extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'expire' => 0, + 'cache_subdir' => true, + 'prefix' => '', + 'path' => '', + 'hash_type' => 'md5', + 'data_compress' => false, + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @param App $app + * @param array $options 参数 + */ + public function __construct(App $app, array $options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + if (empty($this->options['path'])) { + $this->options['path'] = $app->getRuntimePath() . 'cache'; + } + + if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { + $this->options['path'] .= DIRECTORY_SEPARATOR; + } + } + + /** + * 取得变量的存储文件名 + * @access public + * @param string $name 缓存变量名 + * @return string + */ + public function getCacheKey(string $name): string + { + $name = hash($this->options['hash_type'], $name); + + if ($this->options['cache_subdir']) { + // 使用子目录 + $name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2); + } + + if ($this->options['prefix']) { + $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name; + } + + return $this->options['path'] . $name . '.php'; + } + + /** + * 获取缓存数据 + * @param string $name 缓存标识名 + * @return array|null + */ + protected function getRaw(string $name) + { + $filename = $this->getCacheKey($name); + + if (!is_file($filename)) { + return; + } + + $content = @file_get_contents($filename); + + if (false !== $content) { + $expire = (int) substr($content, 8, 12); + if (0 != $expire && time() - $expire > filemtime($filename)) { + //缓存过期删除缓存文件 + $this->unlink($filename); + return; + } + + $content = substr($content, 32); + + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + + return ['content' => $content, 'expire' => $expire]; + } + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + return $this->getRaw($name) !== null; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + + $raw = $this->getRaw($name); + + return is_null($raw) ? $default : $this->unserialize($raw['content']); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间 0为永久 + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $expire = $this->getExpireTime($expire); + $filename = $this->getCacheKey($name); + + $dir = dirname($filename); + + if (!is_dir($dir)) { + try { + mkdir($dir, 0755, true); + } catch (\Exception $e) { + // 创建失败 + } + } + + $data = $this->serialize($value); + + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data, 3); + } + + $data = "\n" . $data; + $result = file_put_contents($filename, $data); + + if ($result) { + clearstatcache(); + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + if ($raw = $this->getRaw($name)) { + $value = $this->unserialize($raw['content']) + $step; + $expire = $raw['expire']; + } else { + $value = $step; + $expire = 0; + } + + return $this->set($name, $value, $expire) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1) + { + return $this->inc($name, -$step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function delete($name): bool + { + $this->writeTimes++; + + return $this->unlink($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + + $dirname = $this->options['path'] . $this->options['prefix']; + + $this->rmdir($dirname); + + return true; + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + foreach ($keys as $key) { + $this->unlink($key); + } + } + + /** + * 判断文件是否存在后,删除 + * @access private + * @param string $path + * @return bool + */ + private function unlink(string $path): bool + { + try { + return is_file($path) && unlink($path); + } catch (\Exception $e) { + return false; + } + } + + /** + * 删除文件夹 + * @param $dirname + * @return bool + */ + private function rmdir($dirname) + { + if (!is_dir($dirname)) { + return false; + } + + $items = new FilesystemIterator($dirname); + + foreach ($items as $item) { + if ($item->isDir() && !$item->isLink()) { + $this->rmdir($item->getPathname()); + } else { + $this->unlink($item->getPathname()); + } + } + + @rmdir($dirname); + + return true; + } + +} diff --git a/vendor/topthink/framework/src/think/cache/driver/Memcache.php b/vendor/topthink/framework/src/think/cache/driver/Memcache.php new file mode 100644 index 0000000..81b988f --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/Memcache.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Memcache缓存类 + */ +class Memcache extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'persistent' => true, + 'prefix' => '', + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct(array $options = []) + { + if (!extension_loaded('memcache')) { + throw new \BadFunctionCallException('not support: memcache'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + $this->handler = new \Memcache; + + // 支持集群 + $hosts = (array) $this->options['host']; + $ports = (array) $this->options['port']; + + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + foreach ($hosts as $i => $host) { + $port = $ports[$i] ?? $ports[0]; + $this->options['timeout'] > 0 ? + $this->handler->addServer($host, (int) $port, $this->options['persistent'], 1, (int) $this->options['timeout']) : + $this->handler->addServer($host, (int) $port, $this->options['persistent'], 1); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + $key = $this->getCacheKey($name); + + return false !== $this->handler->get($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + + $result = $this->handler->get($this->getCacheKey($name)); + + return false !== $result ? $this->unserialize($result) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($this->handler->set($key, $value, 0, $expire)) { + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + + return $this->handler->set($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + + return !$res ? false : $value; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function delete($name, $ttl = false): bool + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + + return $this->handler->flush(); + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + foreach ($keys as $key) { + $this->handler->delete($key); + } + } + +} diff --git a/vendor/topthink/framework/src/think/cache/driver/Memcached.php b/vendor/topthink/framework/src/think/cache/driver/Memcached.php new file mode 100644 index 0000000..e6c5606 --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/Memcached.php @@ -0,0 +1,221 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Memcached缓存类 + */ +class Memcached extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'prefix' => '', + 'username' => '', //账号 + 'password' => '', //密码 + 'option' => [], + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + */ + public function __construct(array $options = []) + { + if (!extension_loaded('memcached')) { + throw new \BadFunctionCallException('not support: memcached'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + $this->handler = new \Memcached; + + if (!empty($this->options['option'])) { + $this->handler->setOptions($this->options['option']); + } + + // 设置连接超时时间(单位:毫秒) + if ($this->options['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']); + } + + // 支持集群 + $hosts = (array) $this->options['host']; + $ports = (array) $this->options['port']; + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + $servers = []; + foreach ($hosts as $i => $host) { + $servers[] = [$host, $ports[$i] ?? $ports[0], 1]; + } + + $this->handler->addServers($servers); + + if ('' != $this->options['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->options['username'], $this->options['password']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + $key = $this->getCacheKey($name); + + return $this->handler->get($key) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + + $result = $this->handler->get($this->getCacheKey($name)); + + return false !== $result ? $this->unserialize($result) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($this->handler->set($key, $value, $expire)) { + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + + return $this->handler->set($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + + return !$res ? false : $value; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function delete($name, $ttl = false): bool + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + + return $this->handler->flush(); + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + $this->handler->deleteMulti($keys); + } + +} diff --git a/vendor/topthink/framework/src/think/cache/driver/Redis.php b/vendor/topthink/framework/src/think/cache/driver/Redis.php new file mode 100644 index 0000000..9a9e596 --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/Redis.php @@ -0,0 +1,250 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好 + * 有需要在业务层实现读写分离、或者使用RedisCluster的需求,请使用Redisd驱动 + * + * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis + * @author 尘缘 <130775@qq.com> + */ +class Redis extends Driver +{ + /** @var \Predis\Client|\Redis */ + protected $handler; + + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'password' => '', + 'select' => 0, + 'timeout' => 0, + 'expire' => 0, + 'persistent' => false, + 'prefix' => '', + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + */ + public function __construct(array $options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + if (extension_loaded('redis')) { + $this->handler = new \Redis; + + +// dump($this->options);exit; + if ($this->options['persistent']) { + $this->handler->pconnect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout'], 'persistent_id_' . $this->options['select']); + } else { + $this->handler->connect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout']); + } + + if ('' != $this->options['password']) { + $this->handler->auth($this->options['password']); + } + } elseif (class_exists('\Predis\Client')) { + $params = []; + foreach ($this->options as $key => $val) { + if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication', 'parameters'])) { + $params[$key] = $val; + unset($this->options[$key]); + } + } + + if ('' == $this->options['password']) { + unset($this->options['password']); + } + + $this->handler = new \Predis\Client($this->options, $params); + + $this->options['prefix'] = ''; + } else { + throw new \BadFunctionCallException('not support: redis'); + } + + if (0 != $this->options['select']) { + $this->handler->select($this->options['select']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + return $this->handler->exists($this->getCacheKey($name)) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + + $value = $this->handler->get($this->getCacheKey($name)); + + if (false === $value || is_null($value)) { + return $default; + } + + return $this->unserialize($value); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($expire) { + $this->handler->setex($key, $expire, $value); + } else { + $this->handler->set($key, $value); + } + + return true; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return $this->handler->incrby($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return $this->handler->decrby($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function delete($name): bool + { + $this->writeTimes++; + + $result = $this->handler->del($this->getCacheKey($name)); + return $result > 0; + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + + $this->handler->flushDB(); + return true; + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + // 指定标签清除 + $this->handler->del($keys); + } + + /** + * 追加(数组)缓存数据 + * @access public + * @param string $name 缓存标识 + * @param mixed $value 数据 + * @return void + */ + public function push(string $name, $value): void + { + $this->handler->sAdd($name, $value); + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 缓存标签 + * @return array + */ + public function getTagItems(string $tag): array + { + return $this->handler->sMembers($tag); + } + +} diff --git a/vendor/topthink/framework/src/think/cache/driver/Wincache.php b/vendor/topthink/framework/src/think/cache/driver/Wincache.php new file mode 100644 index 0000000..8b8e26d --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/Wincache.php @@ -0,0 +1,175 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Wincache缓存驱动 + */ +class Wincache extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct(array $options = []) + { + if (!function_exists('wincache_ucache_info')) { + throw new \BadFunctionCallException('not support: WinCache'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + $this->readTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_exists($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_exists($key) ? $this->unserialize(wincache_ucache_get($key)) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if (wincache_ucache_set($key, $value, $expire)) { + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function delete($name): bool + { + $this->writeTimes++; + + return wincache_ucache_delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + return wincache_ucache_clear(); + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + wincache_ucache_delete($keys); + } + +} diff --git a/vendor/topthink/framework/src/think/console/Command.php b/vendor/topthink/framework/src/think/console/Command.php new file mode 100644 index 0000000..bd3fb20 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/Command.php @@ -0,0 +1,504 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console; + +use Exception; +use InvalidArgumentException; +use LogicException; +use think\App; +use think\Console; +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +abstract class Command +{ + + /** @var Console */ + private $console; + private $name; + private $processTitle; + private $aliases = []; + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $consoleDefinitionMerged = false; + private $consoleDefinitionMergedWithArgs = false; + private $synopsis = []; + private $usages = []; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** @var App */ + protected $app; + + /** + * 构造方法 + * @throws LogicException + * @api + */ + public function __construct() + { + $this->definition = new Definition(); + + $this->configure(); + + if (!$this->name) { + throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); + } + } + + /** + * 忽略验证错误 + */ + public function ignoreValidationErrors(): void + { + $this->ignoreValidationErrors = true; + } + + /** + * 设置控制台 + * @param Console $console + */ + public function setConsole(Console $console = null): void + { + $this->console = $console; + } + + /** + * 获取控制台 + * @return Console + * @api + */ + public function getConsole(): Console + { + return $this->console; + } + + /** + * 设置app + * @param App $app + */ + public function setApp(App $app) + { + $this->app = $app; + } + + /** + * 获取app + * @return App + */ + public function getApp() + { + return $this->app; + } + + /** + * 是否有效 + * @return bool + */ + public function isEnabled(): bool + { + return true; + } + + /** + * 配置指令 + */ + protected function configure() + { + } + + /** + * 执行指令 + * @param Input $input + * @param Output $output + * @return null|int + * @throws LogicException + * @see setCode() + */ + protected function execute(Input $input, Output $output) + { + return $this->app->invoke([$this, 'handle']); + } + + /** + * 用户验证 + * @param Input $input + * @param Output $output + */ + protected function interact(Input $input, Output $output) + { + } + + /** + * 初始化 + * @param Input $input An InputInterface instance + * @param Output $output An OutputInterface instance + */ + protected function initialize(Input $input, Output $output) + { + } + + /** + * 执行 + * @param Input $input + * @param Output $output + * @return int + * @throws Exception + * @see setCode() + * @see execute() + */ + public function run(Input $input, Output $output): int + { + $this->input = $input; + $this->output = $output; + + $this->getSynopsis(true); + $this->getSynopsis(false); + + $this->mergeConsoleDefinition(); + + try { + $input->bind($this->definition); + } catch (Exception $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if (null !== $this->processTitle) { + if (function_exists('cli_set_process_title')) { + if (false === @cli_set_process_title($this->processTitle)) { + if ('Darwin' === PHP_OS) { + $output->writeln('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.'); + } else { + $error = error_get_last(); + trigger_error($error['message'], E_USER_WARNING); + } + } + } elseif (function_exists('setproctitle')) { + setproctitle($this->processTitle); + } elseif (Output::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { + $output->writeln('Install the proctitle PECL to be able to change the process title.'); + } + } + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + $input->validate(); + + $statusCode = $this->execute($input, $output); + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * 合并参数定义 + * @param bool $mergeArgs + */ + public function mergeConsoleDefinition(bool $mergeArgs = true) + { + if (null === $this->console + || (true === $this->consoleDefinitionMerged + && ($this->consoleDefinitionMergedWithArgs || !$mergeArgs)) + ) { + return; + } + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->console->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + } + + $this->definition->addOptions($this->console->getDefinition()->getOptions()); + + $this->consoleDefinitionMerged = true; + if ($mergeArgs) { + $this->consoleDefinitionMergedWithArgs = true; + } + } + + /** + * 设置参数定义 + * @param array|Definition $definition + * @return Command + * @api + */ + public function setDefinition($definition) + { + if ($definition instanceof Definition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->consoleDefinitionMerged = false; + + return $this; + } + + /** + * 获取参数定义 + * @return Definition + * @api + */ + public function getDefinition(): Definition + { + return $this->definition; + } + + /** + * 获取当前指令的参数定义 + * @return Definition + */ + public function getNativeDefinition(): Definition + { + return $this->getDefinition(); + } + + /** + * 添加参数 + * @param string $name 名称 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addArgument(string $name, int $mode = null, string $description = '', $default = null) + { + $this->definition->addArgument(new Argument($name, $mode, $description, $default)); + + return $this; + } + + /** + * 添加选项 + * @param string $name 选项名称 + * @param string $shortcut 别名 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addOption(string $name, string $shortcut = null, int $mode = null, string $description = '', $default = null) + { + $this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default)); + + return $this; + } + + /** + * 设置指令名称 + * @param string $name + * @return Command + * @throws InvalidArgumentException + */ + public function setName(string $name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * 设置进程名称 + * + * PHP 5.5+ or the proctitle PECL library is required + * + * @param string $title The process title + * + * @return $this + */ + public function setProcessTitle($title) + { + $this->processTitle = $title; + + return $this; + } + + /** + * 获取指令名称 + * @return string + */ + public function getName(): string + { + return $this->name ?: ''; + } + + /** + * 设置描述 + * @param string $description + * @return Command + */ + public function setDescription(string $description) + { + $this->description = $description; + + return $this; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription(): string + { + return $this->description ?: ''; + } + + /** + * 设置帮助信息 + * @param string $help + * @return Command + */ + public function setHelp(string $help) + { + $this->help = $help; + + return $this; + } + + /** + * 获取帮助信息 + * @return string + */ + public function getHelp(): string + { + return $this->help ?: ''; + } + + /** + * 描述信息 + * @return string + */ + public function getProcessedHelp(): string + { + $name = $this->name; + + $placeholders = [ + '%command.name%', + '%command.full_name%', + ]; + $replacements = [ + $name, + $_SERVER['PHP_SELF'] . ' ' . $name, + ]; + + return str_replace($placeholders, $replacements, $this->getHelp()); + } + + /** + * 设置别名 + * @param string[] $aliases + * @return Command + * @throws InvalidArgumentException + */ + public function setAliases(iterable $aliases) + { + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * 获取别名 + * @return array + */ + public function getAliases(): array + { + return $this->aliases; + } + + /** + * 获取简介 + * @param bool $short 是否简单的 + * @return string + */ + public function getSynopsis(bool $short = false): string + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * 添加用法介绍 + * @param string $usage + * @return $this + */ + public function addUsage(string $usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * 获取用法介绍 + * @return array + */ + public function getUsages(): array + { + return $this->usages; + } + + /** + * 验证指令名称 + * @param string $name + * @throws InvalidArgumentException + */ + private function validateName(string $name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } + + /** + * 输出表格 + * @param Table $table + * @return string + */ + protected function table(Table $table): string + { + $content = $table->render(); + $this->output->writeln($content); + return $content; + } + +} diff --git a/vendor/topthink/framework/src/think/console/Input.php b/vendor/topthink/framework/src/think/console/Input.php new file mode 100644 index 0000000..9ae9077 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/Input.php @@ -0,0 +1,465 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console; + +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +class Input +{ + + /** + * @var Definition + */ + protected $definition; + + /** + * @var Option[] + */ + protected $options = []; + + /** + * @var Argument[] + */ + protected $arguments = []; + + protected $interactive = true; + + private $tokens; + private $parsed; + + public function __construct($argv = null) + { + if (null === $argv) { + $argv = $_SERVER['argv']; + // 去除命令名 + array_shift($argv); + } + + $this->tokens = $argv; + + $this->definition = new Definition(); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * 绑定实例 + * @param Definition $definition A InputDefinition instance + */ + public function bind(Definition $definition): void + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * 解析参数 + */ + protected function parse(): void + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + $parseOptions = false; + } elseif ($parseOptions && 0 === strpos($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + } + } + + /** + * 解析短选项 + * @param string $token 当前的指令. + */ + private function parseShortOption(string $token): void + { + $name = substr($token, 1); + + if (strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) + && $this->definition->getOptionForShortcut($name[0])->acceptValue() + ) { + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * 解析短选项 + * @param string $name 当前指令 + * @throws \RuntimeException + */ + private function parseShortOptionSet(string $name): void + { + $len = strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * 解析完整选项 + * @param string $token 当前指令 + */ + private function parseLongOption(string $token): void + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1)); + } else { + $this->addLongOption($name, null); + } + } + + /** + * 解析参数 + * @param string $token 当前指令 + * @throws \RuntimeException + */ + private function parseArgument(string $token): void + { + $c = count($this->arguments); + + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + + $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; + + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + + $this->arguments[$arg->getName()][] = $token; + } else { + throw new \RuntimeException('Too many arguments.'); + } + } + + /** + * 添加一个短选项的值 + * @param string $shortcut 短名称 + * @param mixed $value 值 + * @throws \RuntimeException + */ + private function addShortOption(string $shortcut, $value): void + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * 添加一个完整选项的值 + * @param string $name 选项名 + * @param mixed $value 值 + * @throws \RuntimeException + */ + private function addLongOption(string $name, $value): void + { + if (!$this->definition->hasOption($name)) { + throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (false === $value) { + $value = null; + } + + if (null !== $value && !$option->acceptValue()) { + throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value)); + } + + if (null === $value && $option->acceptValue() && count($this->parsed)) { + $next = array_shift($this->parsed); + if (isset($next[0]) && '-' !== $next[0]) { + $value = $next; + } elseif (empty($next)) { + $value = ''; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray()) { + $value = $option->isValueOptional() ? $option->getDefault() : true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + /** + * 获取第一个参数 + * @return string|null + */ + public function getFirstArgument() + { + foreach ($this->tokens as $token) { + if ($token && '-' === $token[0]) { + continue; + } + + return $token; + } + return; + } + + /** + * 检查原始参数是否包含某个值 + * @param string|array $values 需要检查的值 + * @return bool + */ + public function hasParameterOption($values): bool + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value . '=')) { + return true; + } + } + } + + return false; + } + + /** + * 获取原始选项的值 + * @param string|array $values 需要检查的值 + * @param mixed $default 默认值 + * @return mixed The option value + */ + public function getParameterOption($values, $default = false) + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < count($tokens)) { + $token = array_shift($tokens); + + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value . '=')) { + if (false !== $pos = strpos($token, '=')) { + return substr($token, $pos + 1); + } + + return array_shift($tokens); + } + } + } + + return $default; + } + + /** + * 验证输入 + * @throws \RuntimeException + */ + public function validate() + { + if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) { + throw new \RuntimeException('Not enough arguments.'); + } + } + + /** + * 检查输入是否是交互的 + * @return bool + */ + public function isInteractive(): bool + { + return $this->interactive; + } + + /** + * 设置输入的交互 + * @param bool + */ + public function setInteractive(bool $interactive): void + { + $this->interactive = $interactive; + } + + /** + * 获取所有的参数 + * @return Argument[] + */ + public function getArguments(): array + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * 根据名称获取参数 + * @param string $name 参数名 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getArgument(string $name) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return $this->arguments[$name] ?? $this->definition->getArgument($name) + ->getDefault(); + } + + /** + * 设置参数的值 + * @param string $name 参数名 + * @param string $value 值 + * @throws \InvalidArgumentException + */ + public function setArgument(string $name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * 检查是否存在某个参数 + * @param string|int $name 参数名或位置 + * @return bool + */ + public function hasArgument($name): bool + { + return $this->definition->hasArgument($name); + } + + /** + * 获取所有的选项 + * @return Option[] + */ + public function getOptions(): array + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * 获取选项值 + * @param string $name 选项名称 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getOption(string $name) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return $this->options[$name] ?? $this->definition->getOption($name)->getDefault(); + } + + /** + * 设置选项值 + * @param string $name 选项名 + * @param string|bool $value 值 + * @throws \InvalidArgumentException + */ + public function setOption(string $name, $value): void + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * 是否有某个选项 + * @param string $name 选项名 + * @return bool + */ + public function hasOption(string $name): bool + { + return $this->definition->hasOption($name) && isset($this->options[$name]); + } + + /** + * 转义指令 + * @param string $token + * @return string + */ + public function escapeToken(string $token): string + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } + + /** + * 返回传递给命令的参数的字符串 + * @return string + */ + public function __toString() + { + $tokens = array_map(function ($token) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1] . $this->escapeToken($match[2]); + } + + if ($token && '-' !== $token[0]) { + return $this->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/vendor/topthink/framework/src/think/console/LICENSE b/vendor/topthink/framework/src/think/console/LICENSE new file mode 100644 index 0000000..0abe056 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/vendor/topthink/framework/src/think/console/Output.php b/vendor/topthink/framework/src/think/console/Output.php new file mode 100644 index 0000000..13837a7 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/Output.php @@ -0,0 +1,224 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console; + +use Exception; +use think\console\output\Ask; +use think\console\output\Descriptor; +use think\console\output\driver\Buffer; +use think\console\output\driver\Console; +use think\console\output\driver\Nothing; +use think\console\output\Question; +use think\console\output\question\Choice; +use think\console\output\question\Confirmation; +use Throwable; + +/** + * Class Output + * @package think\console + * + * @see \think\console\output\driver\Console::setDecorated + * @method void setDecorated($decorated) + * + * @see \think\console\output\driver\Buffer::fetch + * @method string fetch() + * + * @method void info($message) + * @method void error($message) + * @method void comment($message) + * @method void warning($message) + * @method void highlight($message) + * @method void question($message) + */ +class Output +{ + const VERBOSITY_QUIET = 0; + const VERBOSITY_NORMAL = 1; + const VERBOSITY_VERBOSE = 2; + const VERBOSITY_VERY_VERBOSE = 3; + const VERBOSITY_DEBUG = 4; + + const OUTPUT_NORMAL = 0; + const OUTPUT_RAW = 1; + const OUTPUT_PLAIN = 2; + + private $verbosity = self::VERBOSITY_NORMAL; + + /** @var Buffer|Console|Nothing */ + private $handle = null; + + protected $styles = [ + 'info', + 'error', + 'comment', + 'question', + 'highlight', + 'warning', + ]; + + public function __construct($driver = 'console') + { + $class = '\\think\\console\\output\\driver\\' . ucwords($driver); + + $this->handle = new $class($this); + } + + public function ask(Input $input, $question, $default = null, $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($input, $question); + } + + public function askHidden(Input $input, $question, $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($input, $question); + } + + public function confirm(Input $input, $question, $default = true) + { + return $this->askQuestion($input, new Confirmation($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice(Input $input, $question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default]; + } + + return $this->askQuestion($input, new Choice($question, $choices, $default)); + } + + protected function askQuestion(Input $input, Question $question) + { + $ask = new Ask($input, $this, $question); + $answer = $ask->run(); + + if ($input->isInteractive()) { + $this->newLine(); + } + + return $answer; + } + + protected function block(string $style, string $message): void + { + $this->writeln("<{$style}>{$message}"); + } + + /** + * 输出空行 + * @param int $count + */ + public function newLine(int $count = 1): void + { + $this->write(str_repeat(PHP_EOL, $count)); + } + + /** + * 输出信息并换行 + * @param string $messages + * @param int $type + */ + public function writeln(string $messages, int $type = 0): void + { + $this->write($messages, true, $type); + } + + /** + * 输出信息 + * @param string $messages + * @param bool $newline + * @param int $type + */ + public function write(string $messages, bool $newline = false, int $type = 0): void + { + $this->handle->write($messages, $newline, $type); + } + + public function renderException(Throwable $e): void + { + $this->handle->renderException($e); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity(int $level) + { + $this->verbosity = $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity(): int + { + return $this->verbosity; + } + + public function isQuiet(): bool + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose(): bool + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose(): bool + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug(): bool + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + public function describe($object, array $options = []): void + { + $descriptor = new Descriptor(); + $options = array_merge([ + 'raw_text' => false, + ], $options); + + $descriptor->describe($this, $object, $options); + } + + public function __call($method, $args) + { + if (in_array($method, $this->styles)) { + array_unshift($args, $method); + return call_user_func_array([$this, 'block'], $args); + } + + if ($this->handle && method_exists($this->handle, $method)) { + return call_user_func_array([$this->handle, $method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } + +} diff --git a/vendor/topthink/framework/src/think/console/Table.php b/vendor/topthink/framework/src/think/console/Table.php new file mode 100644 index 0000000..5a861d7 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/Table.php @@ -0,0 +1,300 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console; + +class Table +{ + const ALIGN_LEFT = 1; + const ALIGN_RIGHT = 0; + const ALIGN_CENTER = 2; + + /** + * 头信息数据 + * @var array + */ + protected $header = []; + + /** + * 头部对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @var int + */ + protected $headerAlign = 1; + + /** + * 表格数据(二维数组) + * @var array + */ + protected $rows = []; + + /** + * 单元格对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @var int + */ + protected $cellAlign = 1; + + /** + * 单元格宽度信息 + * @var array + */ + protected $colWidth = []; + + /** + * 表格输出样式 + * @var string + */ + protected $style = 'default'; + + /** + * 表格样式定义 + * @var array + */ + protected $format = [ + 'compact' => [], + 'default' => [ + 'top' => ['+', '-', '+', '+'], + 'cell' => ['|', ' ', '|', '|'], + 'middle' => ['+', '-', '+', '+'], + 'bottom' => ['+', '-', '+', '+'], + 'cross-top' => ['+', '-', '-', '+'], + 'cross-bottom' => ['+', '-', '-', '+'], + ], + 'markdown' => [ + 'top' => [' ', ' ', ' ', ' '], + 'cell' => ['|', ' ', '|', '|'], + 'middle' => ['|', '-', '|', '|'], + 'bottom' => [' ', ' ', ' ', ' '], + 'cross-top' => ['|', ' ', ' ', '|'], + 'cross-bottom' => ['|', ' ', ' ', '|'], + ], + 'borderless' => [ + 'top' => ['=', '=', ' ', '='], + 'cell' => [' ', ' ', ' ', ' '], + 'middle' => ['=', '=', ' ', '='], + 'bottom' => ['=', '=', ' ', '='], + 'cross-top' => ['=', '=', ' ', '='], + 'cross-bottom' => ['=', '=', ' ', '='], + ], + 'box' => [ + 'top' => ['┌', '─', '┬', '┐'], + 'cell' => ['│', ' ', '│', '│'], + 'middle' => ['├', '─', '┼', '┤'], + 'bottom' => ['└', '─', '┴', '┘'], + 'cross-top' => ['├', '─', '┴', '┤'], + 'cross-bottom' => ['├', '─', '┬', '┤'], + ], + 'box-double' => [ + 'top' => ['╔', '═', '╤', '╗'], + 'cell' => ['║', ' ', '│', '║'], + 'middle' => ['╠', '─', '╪', '╣'], + 'bottom' => ['╚', '═', '╧', '╝'], + 'cross-top' => ['╠', '═', '╧', '╣'], + 'cross-bottom' => ['╠', '═', '╤', '╣'], + ], + ]; + + /** + * 设置表格头信息 以及对齐方式 + * @access public + * @param array $header 要输出的Header信息 + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return void + */ + public function setHeader(array $header, int $align = 1): void + { + $this->header = $header; + $this->headerAlign = $align; + + $this->checkColWidth($header); + } + + /** + * 设置输出表格数据 及对齐方式 + * @access public + * @param array $rows 要输出的表格数据(二维数组) + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return void + */ + public function setRows(array $rows, int $align = 1): void + { + $this->rows = $rows; + $this->cellAlign = $align; + + foreach ($rows as $row) { + $this->checkColWidth($row); + } + } + + /** + * 设置全局单元格对齐方式 + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return $this + */ + public function setCellAlign(int $align = 1) + { + $this->cellAlign = $align; + return $this; + } + + /** + * 检查列数据的显示宽度 + * @access public + * @param mixed $row 行数据 + * @return void + */ + protected function checkColWidth($row): void + { + if (is_array($row)) { + foreach ($row as $key => $cell) { + $width = mb_strwidth((string) $cell); + if (!isset($this->colWidth[$key]) || $width > $this->colWidth[$key]) { + $this->colWidth[$key] = $width; + } + } + } + } + + /** + * 增加一行表格数据 + * @access public + * @param mixed $row 行数据 + * @param bool $first 是否在开头插入 + * @return void + */ + public function addRow($row, bool $first = false): void + { + if ($first) { + array_unshift($this->rows, $row); + } else { + $this->rows[] = $row; + } + + $this->checkColWidth($row); + } + + /** + * 设置输出表格的样式 + * @access public + * @param string $style 样式名 + * @return void + */ + public function setStyle(string $style): void + { + $this->style = isset($this->format[$style]) ? $style : 'default'; + } + + /** + * 输出分隔行 + * @access public + * @param string $pos 位置 + * @return string + */ + protected function renderSeparator(string $pos): string + { + $style = $this->getStyle($pos); + $array = []; + + foreach ($this->colWidth as $width) { + $array[] = str_repeat($style[1], $width + 2); + } + + return $style[0] . implode($style[2], $array) . $style[3] . PHP_EOL; + } + + /** + * 输出表格头部 + * @access public + * @return string + */ + protected function renderHeader(): string + { + $style = $this->getStyle('cell'); + $content = $this->renderSeparator('top'); + + foreach ($this->header as $key => $header) { + $array[] = ' ' . str_pad($header, $this->colWidth[$key], $style[1], $this->headerAlign); + } + + if (!empty($array)) { + $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL; + + if (!empty($this->rows)) { + $content .= $this->renderSeparator('middle'); + } + } + + return $content; + } + + protected function getStyle(string $style): array + { + if ($this->format[$this->style]) { + $style = $this->format[$this->style][$style]; + } else { + $style = [' ', ' ', ' ', ' ']; + } + + return $style; + } + + /** + * 输出表格 + * @access public + * @param array $dataList 表格数据 + * @return string + */ + public function render(array $dataList = []): string + { + if (!empty($dataList)) { + $this->setRows($dataList); + } + + // 输出头部 + $content = $this->renderHeader(); + $style = $this->getStyle('cell'); + + if (!empty($this->rows)) { + foreach ($this->rows as $row) { + if (is_string($row) && '-' === $row) { + $content .= $this->renderSeparator('middle'); + } elseif (is_scalar($row)) { + $content .= $this->renderSeparator('cross-top'); + $width = 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) { + return $a + $b; + }); + $array = str_pad($row, $width); + + $content .= $style[0] . ' ' . $array . ' ' . $style[3] . PHP_EOL; + $content .= $this->renderSeparator('cross-bottom'); + } else { + $array = []; + + foreach ($row as $key => $val) { + $width = $this->colWidth[$key]; + // form https://github.com/symfony/console/blob/20c9821c8d1c2189f287dcee709b2f86353ea08f/Helper/Table.php#L467 + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (false !== $encoding = mb_detect_encoding((string) $val, null, true)) { + $width += strlen((string) $val) - mb_strwidth((string) $val, $encoding); + } + $array[] = ' ' . str_pad((string) $val, $width, ' ', $this->cellAlign); + } + + $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL; + } + } + } + + $content .= $this->renderSeparator('bottom'); + + return $content; + } +} diff --git a/vendor/topthink/framework/src/think/console/bin/README.md b/vendor/topthink/framework/src/think/console/bin/README.md new file mode 100644 index 0000000..9acc52f --- /dev/null +++ b/vendor/topthink/framework/src/think/console/bin/README.md @@ -0,0 +1 @@ +console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。 diff --git a/vendor/topthink/framework/src/think/console/bin/hiddeninput.exe b/vendor/topthink/framework/src/think/console/bin/hiddeninput.exe new file mode 100644 index 0000000000000000000000000000000000000000..c8cf65e8d819e6e525121cf6b21f1c2429746038 GIT binary patch literal 9216 zcmeHNe{@sVeZR8hV88~S)=Hp|Mpn({rC^@)BwNOI{ERJXCYlx+k1K6PLHo z_e!z_fhOzeA3JTX&-Z@s{rFOgjEwBlqjr!)9f zjyHz`A+ni`!0Taby{Uj5Y>jQq(k5A+X})PLWAi|{IZbtc8n^^trM{GI=P_15U6d?l zJJ3PW8XjfHpR}6`k{&5@JcEeH_SqQoQbU62o2YS30W)p_t&Fjy*RXQCZt$gCf|ao| zx&3R}m6|-Lfi@pua=$26n(UlnWo$>K67*|+#(qL_An=?l0M02AhOSJDv3;~?1ORfw z76EdK#MpSHqACHLcnJLIYlCSiX4eS@Pr8rN)Xwz0dk7O*y^0_C(Yks2Kvg! z-d-fJ)F9@k?>)m(XqDKIe2OKfhCQde9fpO0ko24yn*4xzX7q+ze`Z*=aJgwV?D?73 zaJ8UkSk|NN>@-|mB*f`EIK7$ElgAB<7p&p`^Vuq$58#;?B^*Bz7&d$B#+AYUC z(^m|`7{lqx&b^5$;i`j|S!+u|lcaQplp_&Nb)!>r>vGh3wb!tW zLq6%bkSt8jO|(vWH>LiPV(Xkp%BiGhl1q!PXXNKVKE!>Y5cHc2%cJOJA{-&ZsSn`T z#8~TA#(HWH4m>uCd+kCMTFgMI*s*n3!iCOwEI`{vGcVhzDu!Lw%-Ea^JATtrF`q3`+#KvvYJ0vM~A}D#LOD zlw`4ncB0U*Jji=--Wz#>I&5?hy;MgYW2u91d8ob=7MWfY`u;7Xe-J{Qsb0=0p|SM2 zG|=~mERIj4?gi)Ew|{LIN#oAsh20k_khIYjJBBN6rrIJ=eQO=nE;rTnPSiaQS$1$# z+|JRh0!IbQIa*f1(TZ}QM;|WO0+jTy(e)ggN4>zqp2E>C>hGPLHjHBh--2%@{EZNE zbUk{<3MABX&20QwK{MxK8`1Vk>^%dO5i@VTfu>NG3$K4NC=hSPsj9UYy`rNO}sBnB9QdKdIk7G+2_amnWstdTYVg z7HgLJGC~XLZG`63GwH8PdO_+G(k6~?J8Wj5mQos#21kC4W#2)guQXI)!z^{@F)U)5 z*re+r(2dib3D4P~%Z6TL=$PIkpmm<_#isu%t=%DcIwNkJhMeJ|bpahHO%8h|y~Ccf zUg#xVk+dyu>Q1O7JZ~8KS>tqi0qK**X*y6yHM71`bT=kFZ=@E%oe2!Km1^2sa>v+onZ%x_>aOJF+N0{i~z|<(IzgT*{0PpQq}E zQpU35@bm;qI?t_znGI&5&4sZV>+%m}w$(4hSDvLk)l<{5XyMlnCl7C%AjM3XnWvVz z{NoFsX)JB)SoqABZxUa*Yq+^^(cbq4mL%^lO12c${z{pf+)|kTTI~nQywyYF6}6|8 zlsN9&{-vwTrTyu<5^90_AsIU-ID#ZG@6d%poU44<**%xVe?`uxf}_Mr$SLHLS|K_N zQnw>(Lr2U=%$-<2D~RSzbG)2W2u^KMDnFFE?GmmbQ)V)fty957F`4OvQ_25E68ITr z5?`suu`|v?r!y=gFOGj$%9IJ zuTP=&2GcnoZZ0qSe6YL-*-lg>Q#>?Ew`a=GDc4vI#<1sNdKn?n7iSj0Orl$-#FMFi zykr>X-Xvi>sVr;92+8*H!r|3L$#o~hXa0z>AmF=z z?|@FF;*S|S0yqsw0j>Z(3mX-HD!|{N-vYc9paC8Ld=|6?00!6(_%lERupO`&um*4k z0b~W>e*uhTe4;V;mq>(ox$9FB`wLt!*DKj~!aOh|fL&#Pg*b??tm%5~_6M#02wqeC zS~wO>TWGnSp^r<0&8f2V6W->w=C+p~daC5e5wNQM*(* z66^}b0(!q3)zq$mu&VnbR#nr3;h5DS*o7{y66=!#;Dy4$pd1ZH<6WEOi0oJ8SxRL* z*v-9@Z^2w%^S(w5dO{_9Duby%2RT~;ppxaE$l()x6&}>7Wcg=u_&>f`Vs8OJGTy{X z2HpG=ThJz<{%|4Qq-~ad0qcrc87n88DHpM(nypwXIkZn<{zIT$ul&BQ?{ApCAZtyr zs2YpNt@x(G*faTU*HCKnAk(G=Tl~>r1QK8LY~J8mFFGoN5iIkYSwlm4Lsj#g4dsE5 zU-4;*Kdh-zv!rT4N$O}Q&n)?v0-9Y)lRFz58^P-KtKonzrfQ1p@0V_10^0||cGRn9 zRG<-#_TEV2nn4{BOh{YVBR4e!V!D?0K%BAlQN!D%M#k1bHypiIHT)5tlj>p0Pp_;+ z!cqC-JIs@JRhB+#teGs$Cib_=(yjRo4OJg^YPg%58aJVsC(LQ?W6%pn!-#aMZwoPcopo^Rn6BE z3=c5&W5~pP(C(-2r;PnH-S0{F`runM0ERCf3rESX$+S(MKOXmKJL9zXF}9-lf^xUs z+bb)+P%L&gV@<4q{6w^xEJ>Y>TQFUeoz0o-yq)jUqww=?wjUO8Y{a5G;DJ0Jr!LL+ zWhgsLuzi&eDrGDn$2DJwpFfH-?SGWbr>qRb?v{P`_%)So)CQgzO^HQ%;y#tJ=knH4 z95jX;^bF#BiuTH^%-j}{9VrZD=R%Q%wselH^p>5 z7d>gWB-st&3Fj%Mt*|tR5iK3J=`xhs&G)I7E>`FO@o7L z@S$B!pYMuzz5DN@X!O4DPm5n@raPJn-Q#o*m*e^5lk$g?0esg%$;>g5QW-|;c=H2GM}bo2tW^D924wmOkrUbWxcQ# z#v6bP%Tdfe~jtCRzAL;-OahZ=#yvUixu2-9fD2j$*|YY`F?0wF-{a# ztr<&kZjZ+81}6ZESqtgW)8kP#s@VLTSUR{}6?U^R*x7RE3Rl&n=VnFFqg9Uqz1n@N9N|=9<4} zuJfy^+}|D9X&vm3MAdqmu0&UMd^=K>b1hLAm_E!$rZC2b;;T~Dl zI`Eo_yRY76uM})|6wk9->of(=9&4jLv5#p@OzS~Yl>@pG)^>6`R+KtL{<4ly4o9WiM!%p_pfROU354)e8PIeE z1_s?#;OX6waNvvb&UQRN(WLbR+}&b#jo&WY-LlwCX}Q*$jGuKYuOGoIoyR(>e}}ix z+t}Q^cEcC8Y{@h}>HmJ^gD!l@gzwHmiBKl26x_lZVZG2UY!`w;RJd122;US&geQdW z3Qq}R!gIo5;ka;0I4c-Jq5X6A6?VzK&c4y!ZXdAUYu{r}*!SBXw?Aor+J4-A(*COb zb^CwV-?3k`zi-cX*c`VzL`RLI(b4MgIrGN z%ojf`E*6)Gg1A9!7q^N##2zsss^V9~-Qt7d!{UDNZ^XY9pA^3@9ui*?e=7c5d`nD; z?}~R(p>y1Kw!>|X4ycYEAkcZa*n-R%y! zqi)Up756UpqwfE7=hfigw$k~G@25gaxF9UGTkV>C(7x1Rbx4jb#|}rxq0vQ!n-c#f J0sQ~1{4brj`U(I5 literal 0 HcmV?d00001 diff --git a/vendor/topthink/framework/src/think/console/command/Clear.php b/vendor/topthink/framework/src/think/console/command/Clear.php new file mode 100644 index 0000000..81878c5 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Clear.php @@ -0,0 +1,64 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; + +class Clear extends Command +{ + protected function configure() + { + // 指令配置 + $this->setName('clear') + ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) + ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file') + ->addOption('log', 'l', Option::VALUE_NONE, 'clear log file') + ->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir') + ->setDescription('Clear runtime file'); + } + + protected function execute(Input $input, Output $output) + { + $runtimePath = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR; + + if ($input->getOption('cache')) { + $path = $runtimePath . 'cache'; + } elseif ($input->getOption('log')) { + $path = $runtimePath . 'log'; + } else { + $path = $input->getOption('path') ?: $runtimePath; + } + + $rmdir = $input->getOption('dir') ? true : false; + $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir); + + $output->writeln("Clear Successed"); + } + + protected function clear(string $path, bool $rmdir): void + { + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if ('.' != $file && '..' != $file && is_dir($path . $file)) { + array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*')); + if ($rmdir) { + rmdir($path . $file); + } + } elseif ('.gitignore' != $file && is_file($path . $file)) { + unlink($path . $file); + } + } + } +} diff --git a/vendor/topthink/framework/src/think/console/command/Help.php b/vendor/topthink/framework/src/think/console/command/Help.php new file mode 100644 index 0000000..d3833b8 --- /dev/null +++ b/vendor/topthink/framework/src/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): void + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(Input $input, Output $output) + { + if (null === $this->command) { + $this->command = $this->getConsole()->find($input->getArgument('command_name')); + } + + $output->describe($this->command, [ + 'raw_text' => $input->getOption('raw'), + ]); + + $this->command = null; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/Lists.php b/vendor/topthink/framework/src/think/console/command/Lists.php new file mode 100644 index 0000000..278c2bd --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Lists.php @@ -0,0 +1,73 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; + +class Lists extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(<<%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(): InputDefinition + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(Input $input, Output $output) + { + $output->describe($this->getConsole(), [ + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + ]); + } + + /** + * {@inheritdoc} + */ + private function createDefinition(): InputDefinition + { + return new InputDefinition([ + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + ]); + } +} diff --git a/vendor/topthink/framework/src/think/console/command/Make.php b/vendor/topthink/framework/src/think/console/command/Make.php new file mode 100644 index 0000000..662b337 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Make.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; + +abstract class Make extends Command +{ + protected $type; + + abstract protected function getStub(); + + protected function configure() + { + $this->addArgument('name', Argument::REQUIRED, "The name of the class"); + } + + protected function execute(Input $input, Output $output) + { + $name = trim($input->getArgument('name')); + + $classname = $this->getClassName($name); + + $pathname = $this->getPathName($classname); + + if (is_file($pathname)) { + $output->writeln('' . $this->type . ':' . $classname . ' already exists!'); + return false; + } + + if (!is_dir(dirname($pathname))) { + mkdir(dirname($pathname), 0755, true); + } + + file_put_contents($pathname, $this->buildClass($classname)); + + $output->writeln('' . $this->type . ':' . $classname . ' created successfully.'); + } + + protected function buildClass(string $name) + { + $stub = file_get_contents($this->getStub()); + + $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + + $class = str_replace($namespace . '\\', '', $name); + + return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [ + $class, + $this->app->config->get('route.action_suffix'), + $namespace, + $this->app->getNamespace(), + ], $stub); + } + + protected function getPathName(string $name): string + { + $name = str_replace('app\\', '', $name); + + return $this->app->getBasePath() . ltrim(str_replace('\\', '/', $name), '/') . '.php'; + } + + protected function getClassName(string $name): string + { + if (strpos($name, '\\') !== false) { + return $name; + } + + if (strpos($name, '@')) { + [$app, $name] = explode('@', $name); + } else { + $app = ''; + } + + if (strpos($name, '/') !== false) { + $name = str_replace('/', '\\', $name); + } + + return $this->getNamespace($app) . '\\' . $name; + } + + protected function getNamespace(string $app): string + { + return 'app' . ($app ? '\\' . $app : ''); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/RouteList.php b/vendor/topthink/framework/src/think/console/command/RouteList.php new file mode 100644 index 0000000..ed579b8 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/RouteList.php @@ -0,0 +1,129 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; +use think\console\Table; +use think\event\RouteLoaded; + +class RouteList extends Command +{ + protected $sortBy = [ + 'rule' => 0, + 'route' => 1, + 'method' => 2, + 'name' => 3, + 'domain' => 4, + ]; + + protected function configure() + { + $this->setName('route:list') + ->addArgument('dir', Argument::OPTIONAL, 'dir name .') + ->addArgument('style', Argument::OPTIONAL, "the style of the table.", 'default') + ->addOption('sort', 's', Option::VALUE_OPTIONAL, 'order by rule name.', 0) + ->addOption('more', 'm', Option::VALUE_NONE, 'show route options.') + ->setDescription('show route list.'); + } + + protected function execute(Input $input, Output $output) + { + $dir = $input->getArgument('dir') ?: ''; + + $filename = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '') . 'route_list.php'; + + if (is_file($filename)) { + unlink($filename); + } elseif (!is_dir(dirname($filename))) { + mkdir(dirname($filename), 0755); + } + + $content = $this->getRouteList($dir); + file_put_contents($filename, 'Route List' . PHP_EOL . $content); + } + + protected function getRouteList(string $dir = null): string + { + $this->app->route->setTestMode(true); + $this->app->route->clear(); + + if ($dir) { + $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR; + } else { + $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR; + } + + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if (strpos($file, '.php')) { + include $path . $file; + } + } + + //触发路由载入完成事件 + $this->app->event->trigger(RouteLoaded::class); + + $table = new Table(); + + if ($this->input->hasOption('more')) { + $header = ['Rule', 'Route', 'Method', 'Name', 'Domain', 'Option', 'Pattern']; + } else { + $header = ['Rule', 'Route', 'Method', 'Name']; + } + + $table->setHeader($header); + + $routeList = $this->app->route->getRuleList(); + $rows = []; + + foreach ($routeList as $item) { + $item['route'] = $item['route'] instanceof \Closure ? '' : $item['route']; + + if ($this->input->hasOption('more')) { + $item = [$item['rule'], $item['route'], $item['method'], $item['name'], $item['domain'], json_encode($item['option']), json_encode($item['pattern'])]; + } else { + $item = [$item['rule'], $item['route'], $item['method'], $item['name']]; + } + + $rows[] = $item; + } + + if ($this->input->getOption('sort')) { + $sort = strtolower($this->input->getOption('sort')); + + if (isset($this->sortBy[$sort])) { + $sort = $this->sortBy[$sort]; + } + + uasort($rows, function ($a, $b) use ($sort) { + $itemA = $a[$sort] ?? null; + $itemB = $b[$sort] ?? null; + + return strcasecmp($itemA, $itemB); + }); + } + + $table->setRows($rows); + + if ($this->input->getArgument('style')) { + $style = $this->input->getArgument('style'); + $table->setStyle($style); + } + + return $this->table($table); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/RunServer.php b/vendor/topthink/framework/src/think/console/command/RunServer.php new file mode 100644 index 0000000..82d2744 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/RunServer.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; + +class RunServer extends Command +{ + public function configure() + { + $this->setName('run') + ->addOption('host', 'H', Option::VALUE_OPTIONAL, + 'The host to server the application on', '0.0.0.0') + ->addOption('port', 'p', Option::VALUE_OPTIONAL, + 'The port to server the application on', 8000) + ->addOption('root', 'r', Option::VALUE_OPTIONAL, + 'The document root of the application', '') + ->setDescription('PHP Built-in Server for ThinkPHP'); + } + + public function execute(Input $input, Output $output) + { + $host = $input->getOption('host'); + $port = $input->getOption('port'); + $root = $input->getOption('root'); + if (empty($root)) { + $root = $this->app->getRootPath() . 'public'; + } + + $command = sprintf( + 'php -S %s:%d -t %s %s', + $host, + $port, + escapeshellarg($root), + escapeshellarg($root . DIRECTORY_SEPARATOR . 'router.php') + ); + + $output->writeln(sprintf('ThinkPHP Development server is started On ', '0.0.0.0' == $host ? '127.0.0.1' : $host, $port)); + $output->writeln(sprintf('You can exit with `CTRL-C`')); + $output->writeln(sprintf('Document root is: %s', $root)); + passthru($command); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php b/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php new file mode 100644 index 0000000..3aae1db --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class ServiceDiscover extends Command +{ + public function configure() + { + $this->setName('service:discover') + ->setDescription('Discover Services for ThinkPHP'); + } + + public function execute(Input $input, Output $output) + { + if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) { + $packages = json_decode(@file_get_contents($path), true); + + $services = []; + foreach ($packages as $package) { + if (!empty($package['extra']['think']['services'])) { + $services = array_merge($services, (array) $package['extra']['think']['services']); + } + } + + $header = '// This file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL . 'declare (strict_types = 1);' . PHP_EOL; + + $content = 'app->getRootPath() . 'vendor/services.php', $content); + + $output->writeln('Succeed!'); + } + } +} diff --git a/vendor/topthink/framework/src/think/console/command/VendorPublish.php b/vendor/topthink/framework/src/think/console/command/VendorPublish.php new file mode 100644 index 0000000..7b43762 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/VendorPublish.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console\command; + +use think\console\Command; +use think\console\input\Option; + +class VendorPublish extends Command +{ + public function configure() + { + $this->setName('vendor:publish') + ->addOption('force', 'f', Option::VALUE_NONE, 'Overwrite any existing files') + ->setDescription('Publish any publishable assets from vendor packages'); + } + + public function handle() + { + + $force = $this->input->getOption('force'); + + if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) { + $packages = json_decode(@file_get_contents($path), true); + + foreach ($packages as $package) { + //配置 + $configDir = $this->app->getConfigPath(); + + if (!empty($package['extra']['think']['config'])) { + + $installPath = $this->app->getRootPath() . 'vendor/' . $package['name'] . DIRECTORY_SEPARATOR; + + foreach ((array) $package['extra']['think']['config'] as $name => $file) { + + $target = $configDir . $name . '.php'; + $source = $installPath . $file; + + if (is_file($target) && !$force) { + $this->output->info("File {$target} exist!"); + continue; + } + + if (!is_file($source)) { + $this->output->info("File {$source} not exist!"); + continue; + } + + copy($source, $target); + } + } + } + + $this->output->writeln('Succeed!'); + } + } +} diff --git a/vendor/topthink/framework/src/think/console/command/Version.php b/vendor/topthink/framework/src/think/console/command/Version.php new file mode 100644 index 0000000..beb49d2 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Version.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class Version extends Command +{ + protected function configure() + { + // 指令配置 + $this->setName('version') + ->setDescription('show thinkphp framework version'); + } + + protected function execute(Input $input, Output $output) + { + $output->writeln('v' . $this->app->version()); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Command.php b/vendor/topthink/framework/src/think/console/command/make/Command.php new file mode 100644 index 0000000..88e665a --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Command.php @@ -0,0 +1,55 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; +use think\console\input\Argument; + +class Command extends Make +{ + protected $type = "Command"; + + protected function configure() + { + parent::configure(); + $this->setName('make:command') + ->addArgument('commandName', Argument::OPTIONAL, "The name of the command") + ->setDescription('Create a new command class'); + } + + protected function buildClass(string $name): string + { + $commandName = $this->input->getArgument('commandName') ?: strtolower(basename($name)); + $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + + $class = str_replace($namespace . '\\', '', $name); + $stub = file_get_contents($this->getStub()); + + return str_replace(['{%commandName%}', '{%className%}', '{%namespace%}', '{%app_namespace%}'], [ + $commandName, + $class, + $namespace, + $this->app->getNamespace(), + ], $stub); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'command.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\command'; + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Controller.php b/vendor/topthink/framework/src/think/console/command/make/Controller.php new file mode 100644 index 0000000..582cffb --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Controller.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; +use think\console\input\Option; + +class Controller extends Make +{ + + protected $type = "Controller"; + + protected function configure() + { + parent::configure(); + $this->setName('make:controller') + ->addOption('api', null, Option::VALUE_NONE, 'Generate an api controller class.') + ->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.') + ->setDescription('Create a new resource controller class'); + } + + protected function getStub(): string + { + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + if ($this->input->getOption('api')) { + return $stubPath . 'controller.api.stub'; + } + + if ($this->input->getOption('plain')) { + return $stubPath . 'controller.plain.stub'; + } + + return $stubPath . 'controller.stub'; + } + + protected function getClassName(string $name): string + { + return parent::getClassName($name) . ($this->app->config->get('route.controller_suffix') ? 'Controller' : ''); + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\controller'; + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Event.php b/vendor/topthink/framework/src/think/console/command/make/Event.php new file mode 100644 index 0000000..a4676d8 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Event.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\make; + +use think\console\command\Make; + +class Event extends Make +{ + protected $type = "Event"; + + protected function configure() + { + parent::configure(); + $this->setName('make:event') + ->setDescription('Create a new event class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'event.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\event'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Listener.php b/vendor/topthink/framework/src/think/console/command/make/Listener.php new file mode 100644 index 0000000..bb29668 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Listener.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\make; + +use think\console\command\Make; + +class Listener extends Make +{ + protected $type = "Listener"; + + protected function configure() + { + parent::configure(); + $this->setName('make:listener') + ->setDescription('Create a new listener class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'listener.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\listener'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Middleware.php b/vendor/topthink/framework/src/think/console/command/make/Middleware.php new file mode 100644 index 0000000..80f4563 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Middleware.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Middleware extends Make +{ + protected $type = "Middleware"; + + protected function configure() + { + parent::configure(); + $this->setName('make:middleware') + ->setDescription('Create a new middleware class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'middleware.stub'; + } + + protected function getNamespace(string $app): string + { + return 'app\\middleware'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Model.php b/vendor/topthink/framework/src/think/console/command/make/Model.php new file mode 100644 index 0000000..acb37e7 --- /dev/null +++ b/vendor/topthink/framework/src/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(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\model'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Service.php b/vendor/topthink/framework/src/think/console/command/make/Service.php new file mode 100644 index 0000000..18bd54e --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Service.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Service extends Make +{ + protected $type = "Service"; + + protected function configure() + { + parent::configure(); + $this->setName('make:service') + ->setDescription('Create a new Service class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'service.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\service'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Subscribe.php b/vendor/topthink/framework/src/think/console/command/make/Subscribe.php new file mode 100644 index 0000000..4203986 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Subscribe.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\make; + +use think\console\command\Make; + +class Subscribe extends Make +{ + protected $type = "Subscribe"; + + protected function configure() + { + parent::configure(); + $this->setName('make:subscribe') + ->setDescription('Create a new subscribe class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'subscribe.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\subscribe'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Validate.php b/vendor/topthink/framework/src/think/console/command/make/Validate.php new file mode 100644 index 0000000..4926e20 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Validate.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Validate extends Make +{ + protected $type = "Validate"; + + protected function configure() + { + parent::configure(); + $this->setName('make:validate') + ->setDescription('Create a validate class'); + } + + protected function getStub(): string + { + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + return $stubPath . 'validate.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\validate'; + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub new file mode 100644 index 0000000..e9121cd --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub @@ -0,0 +1,26 @@ +setName('{%commandName%}') + ->setDescription('the {%commandName%} command'); + } + + protected function execute(Input $input, Output $output) + { + // 指令输出 + $output->writeln('{%commandName%}'); + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub new file mode 100644 index 0000000..5d3383d --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub @@ -0,0 +1,64 @@ + ['规则1','规则2'...] + * + * @var array + */ + protected $rule = []; + + /** + * 定义错误信息 + * 格式:'字段名.规则名' => '错误信息' + * + * @var array + */ + protected $message = []; +} diff --git a/vendor/topthink/framework/src/think/console/command/optimize/Route.php b/vendor/topthink/framework/src/think/console/command/optimize/Route.php new file mode 100644 index 0000000..bd4f79c --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/optimize/Route.php @@ -0,0 +1,67 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; +use think\event\RouteLoaded; + +class Route extends Command +{ + protected function configure() + { + $this->setName('optimize:route') + ->addArgument('dir', Argument::OPTIONAL, 'dir name .') + ->setDescription('Build app route cache.'); + } + + protected function execute(Input $input, Output $output) + { + $dir = $input->getArgument('dir') ?: ''; + + $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : ''); + + $filename = $path . 'route.php'; + if (is_file($filename)) { + unlink($filename); + } + + file_put_contents($filename, $this->buildRouteCache($dir)); + $output->writeln('Succeed!'); + } + + protected function buildRouteCache(string $dir = null): string + { + $this->app->route->clear(); + $this->app->route->lazy(false); + + // 路由检测 + $path = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR ; + + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if (strpos($file, '.php')) { + include $path . $file; + } + } + + //触发路由载入完成事件 + $this->app->event->trigger(RouteLoaded::class); + + $content = 'app->route->getName()) . '\');'; + return $content; + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/optimize/Schema.php b/vendor/topthink/framework/src/think/console/command/optimize/Schema.php new file mode 100644 index 0000000..77b06e4 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/optimize/Schema.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; + +class Schema extends Command +{ + protected function configure() + { + $this->setName('optimize:schema') + ->addArgument('dir', Argument::OPTIONAL, 'dir name .') + ->addOption('db', null, Option::VALUE_REQUIRED, 'db name .') + ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') + ->setDescription('Build database schema cache.'); + } + + protected function execute(Input $input, Output $output) + { + $dir = $input->getArgument('dir') ?: ''; + + $schemaPath = $this->app->db->getConnection()->getConfig('schema_cache_path'); + + if (!is_dir($schemaPath)) { + mkdir($schemaPath, 0755, true); + } + + if ($input->hasOption('table')) { + $table = $input->getOption('table'); + if (false === strpos($table, '.')) { + $dbName = $this->app->db->getConnection()->getConfig('database'); + } + + $tables[] = $table; + } elseif ($input->hasOption('db')) { + $dbName = $input->getOption('db'); + $tables = $this->app->db->getConnection()->getTables($dbName); + } else { + if ($dir) { + $appPath = $this->app->getBasePath() . $dir . DIRECTORY_SEPARATOR; + $namespace = 'app\\' . $dir; + } else { + $appPath = $this->app->getBasePath(); + $namespace = 'app'; + } + + $path = $appPath . 'model'; + $list = is_dir($path) ? scandir($path) : []; + + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + + $output->writeln('Succeed!'); + return; + } + + $db = isset($dbName) ? $dbName . '.' : ''; + $this->buildDataBaseSchema($schemaPath, $tables, $db); + + $output->writeln('Succeed!'); + } + + protected function buildModelSchema(string $class): void + { + $reflect = new \ReflectionClass($class); + if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { + /** @var \think\Model $model */ + $model = new $class; + + $table = $model->getTable(); + $dbName = $model->getConnection()->getConfig('database'); + $path = $model->getConnection()->getConfig('schema_cache_path'); + if (!is_dir($path)) { + mkdir($path, 0755, true); + } + $content = 'db()->getConnection()->getTableFieldsInfo($table); + $content .= var_export($info, true) . ';'; + + file_put_contents($path . $dbName . '.' . $table . '.php', $content); + } + } + + protected function buildDataBaseSchema(string $path, array $tables, string $db): void + { + if ('' == $db) { + $dbName = $this->app->db->getConnection()->getConfig('database') . '.'; + } else { + $dbName = $db; + } + + foreach ($tables as $table) { + $content = 'app->db->getConnection()->getTableFieldsInfo($db . $table); + $content .= var_export($info, true) . ';'; + file_put_contents($path . $dbName . $table . '.php', $content); + } + } +} diff --git a/vendor/topthink/framework/src/think/console/input/Argument.php b/vendor/topthink/framework/src/think/console/input/Argument.php new file mode 100644 index 0000000..4fa3e3c --- /dev/null +++ b/vendor/topthink/framework/src/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(string $name, int $mode = null, string $description = '', $default = null) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + + $this->setDefault($default); + } + + /** + * 获取参数名 + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * 是否必须 + * @return bool + */ + public function isRequired(): bool + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * 该参数是否接受数组 + * @return bool + */ + public function isArray(): bool + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null): void + { + if (self::REQUIRED === $this->mode && null !== $default) { + throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * 获取默认值 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription(): string + { + return $this->description; + } +} diff --git a/vendor/topthink/framework/src/think/console/input/Definition.php b/vendor/topthink/framework/src/think/console/input/Definition.php new file mode 100644 index 0000000..ccf02a0 --- /dev/null +++ b/vendor/topthink/framework/src/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): void + { + $arguments = []; + $options = []; + foreach ($definition as $item) { + if ($item instanceof Option) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * 设置参数 + * @param Argument[] $arguments 参数数组 + */ + public function setArguments(array $arguments = []): void + { + $this->arguments = []; + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * 添加参数 + * @param Argument[] $arguments 参数数组 + * @api + */ + public function addArguments(array $arguments = []): void + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * 添加一个参数 + * @param Argument $argument 参数 + * @throws \LogicException + */ + public function addArgument(Argument $argument): void + { + if (isset($this->arguments[$argument->getName()])) { + throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if ($this->hasAnArrayArgument) { + throw new \LogicException('Cannot add an argument after an array argument.'); + } + + if ($argument->isRequired() && $this->hasOptional) { + throw new \LogicException('Cannot add a required argument after an optional one.'); + } + + if ($argument->isArray()) { + $this->hasAnArrayArgument = true; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->hasOptional = true; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * 根据名称或者位置获取参数 + * @param string|int $name 参数名或者位置 + * @return Argument 参数 + * @throws \InvalidArgumentException + */ + public function getArgument($name): Argument + { + if (!$this->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * 根据名称或位置检查是否具有某个参数 + * @param string|int $name 参数名或者位置 + * @return bool + * @api + */ + public function hasArgument($name): bool + { + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * 获取所有的参数 + * @return Argument[] 参数数组 + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * 获取参数数量 + * @return int + */ + public function getArgumentCount(): int + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * 获取必填的参数的数量 + * @return int + */ + public function getArgumentRequiredCount(): int + { + return $this->requiredCount; + } + + /** + * 获取参数默认值 + * @return array + */ + public function getArgumentDefaults(): array + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * 设置选项 + * @param Option[] $options 选项数组 + */ + public function setOptions(array $options = []): void + { + $this->options = []; + $this->shortcuts = []; + $this->addOptions($options); + } + + /** + * 添加选项 + * @param Option[] $options 选项数组 + * @api + */ + public function addOptions(array $options = []): void + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * 添加一个选项 + * @param Option $option 选项 + * @throws \LogicException + * @api + */ + public function addOption(Option $option): void + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) + && !$option->equals($this->options[$this->shortcuts[$shortcut]]) + ) { + throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + } + + /** + * 根据名称获取选项 + * @param string $name 选项名 + * @return Option + * @throws \InvalidArgumentException + * @api + */ + public function getOption(string $name): Option + { + if (!$this->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * 根据名称检查是否有这个选项 + * @param string $name 选项名 + * @return bool + * @api + */ + public function hasOption(string $name): bool + { + return isset($this->options[$name]); + } + + /** + * 获取所有选项 + * @return Option[] + * @api + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * 根据名称检查某个选项是否有短名称 + * @param string $name 短名称 + * @return bool + */ + public function hasShortcut(string $name): bool + { + return isset($this->shortcuts[$name]); + } + + /** + * 根据短名称获取选项 + * @param string $shortcut 短名称 + * @return Option + */ + public function getOptionForShortcut(string $shortcut): Option + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * 获取所有选项的默认值 + * @return array + */ + public function getOptionDefaults(): array + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * 根据短名称获取选项名 + * @param string $shortcut 短名称 + * @return string + * @throws \InvalidArgumentException + */ + private function shortcutToName(string $shortcut): string + { + if (!isset($this->shortcuts[$shortcut])) { + throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * 获取该指令的介绍 + * @param bool $short 是否简洁介绍 + * @return string + */ + public function getSynopsis(bool $short = false): string + { + $elements = []; + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf(' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : ''); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + } + } + + if (count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + foreach ($this->getArguments() as $argument) { + $element = '<' . $argument->getName() . '>'; + if (!$argument->isRequired()) { + $element = '[' . $element . ']'; + } elseif ($argument->isArray()) { + $element .= ' (' . $element . ')'; + } + + if ($argument->isArray()) { + $element .= '...'; + } + + $elements[] = $element; + } + + return implode(' ', $elements); + } +} diff --git a/vendor/topthink/framework/src/think/console/input/Option.php b/vendor/topthink/framework/src/think/console/input/Option.php new file mode 100644 index 0000000..e5707c9 --- /dev/null +++ b/vendor/topthink/framework/src/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/vendor/topthink/framework/src/think/console/output/Ask.php b/vendor/topthink/framework/src/think/console/output/Ask.php new file mode 100644 index 0000000..56821c7 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/Ask.php @@ -0,0 +1,336 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +use think\console\Input; +use think\console\Output; +use think\console\output\question\Choice; +use think\console\output\question\Confirmation; + +class Ask +{ + private static $stty; + + private static $shell; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** @var Question */ + protected $question; + + public function __construct(Input $input, Output $output, Question $question) + { + $this->input = $input; + $this->output = $output; + $this->question = $question; + } + + public function run() + { + if (!$this->input->isInteractive()) { + return $this->question->getDefault(); + } + + if (!$this->question->getValidator()) { + return $this->doAsk(); + } + + $that = $this; + + $interviewer = function () use ($that) { + return $that->doAsk(); + }; + + return $this->validateAttempts($interviewer); + } + + protected function doAsk() + { + $this->writePrompt(); + + $inputStream = STDIN; + $autocomplete = $this->question->getAutocompleterValues(); + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = false; + if ($this->question->isHidden()) { + try { + $ret = trim($this->getHiddenResponse($inputStream)); + } catch (\RuntimeException $e) { + if (!$this->question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new \RuntimeException('Aborted'); + } + $ret = trim($ret); + } + } else { + $ret = trim($this->autocomplete($inputStream)); + } + + $ret = strlen($ret) > 0 ? $ret : $this->question->getDefault(); + + if ($normalizer = $this->question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + private function autocomplete($inputStream) + { + $autocomplete = $this->question->getAutocompleterValues(); + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -icanon -echo'); + + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + if ("\177" === $c) { + if (0 === $numMatches && 0 !== $i) { + --$i; + $this->output->write("\033[1D"); + } + + if ($i === 0) { + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + } else { + $numMatches = 0; + } + + $ret = substr($ret, 0, $i); + } elseif ("\033" === $c) { + $c .= fread($inputStream, 2); + + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = $matches[$ofs]; + $this->output->write(substr($ret, $i)); + $i = strlen($ret); + } + + if ("\n" === $c) { + $this->output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + $this->output->write($c); + $ret .= $c; + ++$i; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + if (0 === strpos($value, $ret) && $i !== strlen($value)) { + $matches[$numMatches++] = $value; + } + } + } + + $this->output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + $this->output->write("\0337"); + $this->output->highlight(substr($matches[$ofs], $i)); + $this->output->write("\0338"); + } + } + + shell_exec(sprintf('stty %s', $sttyMode)); + + return $ret; + } + + protected function getHiddenResponse($inputStream) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $exe = __DIR__ . '/../bin/hiddeninput.exe'; + + $value = rtrim(shell_exec($exe)); + $this->output->writeln(''); + + return $value; + } + + if ($this->hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($inputStream, 4096); + shell_exec(sprintf('stty %s', $sttyMode)); + + if (false === $value) { + throw new \RuntimeException('Aborted'); + } + + $value = trim($value); + $this->output->writeln(''); + + return $value; + } + + if (false !== $shell = $this->getShell()) { + $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; + $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); + $value = rtrim(shell_exec($command)); + $this->output->writeln(''); + + return $value; + } + + throw new \RuntimeException('Unable to hide the response.'); + } + + protected function validateAttempts($interviewer) + { + /** @var \Exception $error */ + $error = null; + $attempts = $this->question->getMaxAttempts(); + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->output->error($error->getMessage()); + } + + try { + return call_user_func($this->question->getValidator(), $interviewer()); + } catch (\Exception $error) { + } + } + + throw $error; + } + + /** + * 显示问题的提示信息 + */ + protected function writePrompt() + { + $text = $this->question->getQuestion(); + $default = $this->question->getDefault(); + + switch (true) { + case null === $default: + $text = sprintf(' %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/vendor/topthink/framework/src/think/console/output/Descriptor.php b/vendor/topthink/framework/src/think/console/output/Descriptor.php new file mode 100644 index 0000000..8582b59 --- /dev/null +++ b/vendor/topthink/framework/src/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 string|mixed + */ + 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 = $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 string|mixed + */ + 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 = $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 string|mixed + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, strlen($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('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 string|mixed + */ + 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 string|mixed + */ + protected function describeConsole(Console $console, array $options = []) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ConsoleDescription($console, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->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/vendor/topthink/framework/src/think/console/output/Formatter.php b/vendor/topthink/framework/src/think/console/output/Formatter.php new file mode 100644 index 0000000..1b97ca3 --- /dev/null +++ b/vendor/topthink/framework/src/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 = $matches[3][$i][0] ?? ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { + $output .= $this->applyCurrentStyle($text); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset)); + + return str_replace('\\<', '<', $output); + } + + /** + * @return StyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * 根据字符串创建新的样式实例 + * @param string $string + * @return Style|bool + */ + private function createStyleFromString($string) + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) { + return false; + } + + $style = new Style(); + foreach ($matches as $match) { + array_shift($match); + + if ('fg' == $match[0]) { + $style->setForeground($match[1]); + } elseif ('bg' == $match[0]) { + $style->setBackground($match[1]); + } else { + try { + $style->setOption($match[1]); + } catch (\InvalidArgumentException $e) { + return false; + } + } + } + + return $style; + } + + /** + * 从堆栈应用样式到文字 + * @param string $text 文字 + * @return string + */ + private function applyCurrentStyle($text) + { + return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; + } +} diff --git a/vendor/topthink/framework/src/think/console/output/Question.php b/vendor/topthink/framework/src/think/console/output/Question.php new file mode 100644 index 0000000..03975f2 --- /dev/null +++ b/vendor/topthink/framework/src/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/vendor/topthink/framework/src/think/console/output/descriptor/Console.php b/vendor/topthink/framework/src/think/console/output/descriptor/Console.php new file mode 100644 index 0000000..ff9f464 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/descriptor/Console.php @@ -0,0 +1,153 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\descriptor; + +use think\Console as ThinkConsole; +use think\console\Command; + +class Console +{ + + const GLOBAL_NAMESPACE = '_global'; + + /** + * @var ThinkConsole + */ + private $console; + + /** + * @var null|string + */ + private $namespace; + + /** + * @var array + */ + private $namespaces; + + /** + * @var Command[] + */ + private $commands; + + /** + * @var Command[] + */ + private $aliases; + + /** + * 构造方法 + * @param ThinkConsole $console + * @param string|null $namespace + */ + public function __construct(ThinkConsole $console, $namespace = null) + { + $this->console = $console; + $this->namespace = $namespace; + } + + /** + * @return array + */ + public function getNamespaces(): array + { + if (null === $this->namespaces) { + $this->inspectConsole(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands(): array + { + if (null === $this->commands) { + $this->inspectConsole(); + } + + return $this->commands; + } + + /** + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function getCommand(string $name): Command + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); + } + + return $this->commands[$name] ?? $this->aliases[$name]; + } + + private function inspectConsole(): void + { + $this->commands = []; + $this->namespaces = []; + + $all = $this->console->all($this->namespace ? $this->console->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = []; + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (is_string($command)) { + $command = new $command(); + } + + if (!$command->getName()) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; + } + } + + /** + * @param array $commands + * @return array + */ + private function sortCommands(array $commands): array + { + $namespacedCommands = []; + foreach ($commands as $name => $command) { + $key = $this->console->extractNamespace($name, 1); + if (!$key) { + $key = self::GLOBAL_NAMESPACE; + } + + $namespacedCommands[$key][$name] = $command; + } + ksort($namespacedCommands); + + foreach ($namespacedCommands as &$commandsSet) { + ksort($commandsSet); + } + // unset reference to keep scope clear + unset($commandsSet); + + return $namespacedCommands; + } +} diff --git a/vendor/topthink/framework/src/think/console/output/driver/Buffer.php b/vendor/topthink/framework/src/think/console/output/driver/Buffer.php new file mode 100644 index 0000000..576f31a --- /dev/null +++ b/vendor/topthink/framework/src/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, bool $newline = false, int $options = 0) + { + $messages = (array) $messages; + + foreach ($messages as $message) { + $this->buffer .= $message; + } + if ($newline) { + $this->buffer .= "\n"; + } + } + + public function renderException(\Throwable $e) + { + // do nothing + } + +} diff --git a/vendor/topthink/framework/src/think/console/output/driver/Console.php b/vendor/topthink/framework/src/think/console/output/driver/Console.php new file mode 100644 index 0000000..31bdf1f --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/driver/Console.php @@ -0,0 +1,368 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; +use think\console\output\Formatter; + +class Console +{ + + /** @var Resource */ + private $stdout; + + /** @var Formatter */ + private $formatter; + + private $terminalDimensions; + + /** @var Output */ + private $output; + + public function __construct(Output $output) + { + $this->output = $output; + $this->formatter = new Formatter(); + $this->stdout = $this->openOutputStream(); + $decorated = $this->hasColorSupport($this->stdout); + $this->formatter->setDecorated($decorated); + } + + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + public function write($messages, bool $newline = false, int $type = 0, $stream = null) + { + if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $messages = (array) $messages; + + foreach ($messages as $message) { + switch ($type) { + case Output::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case Output::OUTPUT_RAW: + break; + case Output::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); + } + + $this->doWrite($message, $newline, $stream); + } + } + + public function renderException(\Throwable $e) + { + $stderr = $this->openErrorStream(); + $decorated = $this->hasColorSupport($stderr); + $this->formatter->setDecorated($decorated); + + do { + $title = sprintf(' [%s] ', get_class($e)); + + $len = $this->stringWidth($title); + + $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; + + if (defined('HHVM_VERSION') && $width > 1 << 31) { + $width = 1 << 31; + } + $lines = []; + foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + + $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $line)) + 4; + $lines[] = [$line, $lineLength]; + + $len = max($lineLength, $len); + } + } + + $messages = ['', '']; + $messages[] = $emptyLine = sprintf('%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(): array + { + if ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { + return [(int) $matches[1], (int) $matches[2]]; + } + if (preg_match('/^(\d+)x(\d+)$/', $this->getMode(), $matches)) { + return [(int) $matches[1], (int) $matches[2]]; + } + } + + if ($sttyString = $this->getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + return [(int) $matches[2], (int) $matches[1]]; + } + if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + return [(int) $matches[2], (int) $matches[1]]; + } + } + + return [null, null]; + } + + /** + * 获取stty列数 + * @return string + */ + private function getSttyColumns() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + return $info; + } + return; + } + + /** + * 获取终端模式 + * @return string 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 $string): int + { + if (!function_exists('mb_strwidth')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + private function splitStringByWidth(string $string, int $width): array + { + if (!function_exists('mb_strwidth')) { + return str_split($string, $width); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = []; + $line = ''; + foreach (preg_split('//u', $utf8String) as $char) { + if (mb_strwidth($line . $char, 'utf8') <= $width) { + $line .= $char; + continue; + } + $lines[] = str_pad($line, $width); + $line = $char; + } + if (strlen($line)) { + $lines[] = count($lines) ? str_pad($line, $width) : $line; + } + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + private function isRunningOS400(): bool + { + $checks = [ + function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ]; + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * 当前环境是否支持写入控制台输出到stdout. + * + * @return bool + */ + protected function hasStdoutSupport(): bool + { + return false === $this->isRunningOS400(); + } + + /** + * 当前环境是否支持写入控制台输出到stderr. + * + * @return bool + */ + protected function hasStderrSupport(): bool + { + return false === $this->isRunningOS400(); + } + + /** + * @return resource + */ + private function openOutputStream() + { + if (!$this->hasStdoutSupport()) { + return fopen('php://output', 'w'); + } + return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); + } + + /** + * @return resource + */ + private function openErrorStream() + { + return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); + } + + /** + * 将消息写入到输出。 + * @param string $message 消息 + * @param bool $newline 是否另起一行 + * @param null $stream + */ + protected function doWrite($message, $newline, $stream = null) + { + if (null === $stream) { + $stream = $this->stdout; + } + if (false === @fwrite($stream, $message . ($newline ? PHP_EOL : ''))) { + throw new \RuntimeException('Unable to write output.'); + } + + fflush($stream); + } + + /** + * 是否支持着色 + * @param $stream + * @return bool + */ + protected function hasColorSupport($stream): bool + { + if (DIRECTORY_SEPARATOR === '\\') { + return + '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return function_exists('posix_isatty') && @posix_isatty($stream); + } + +} diff --git a/vendor/topthink/framework/src/think/console/output/driver/Nothing.php b/vendor/topthink/framework/src/think/console/output/driver/Nothing.php new file mode 100644 index 0000000..a7cc49e --- /dev/null +++ b/vendor/topthink/framework/src/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, bool $newline = false, int $options = 0) + { + // do nothing + } + + public function renderException(\Throwable $e) + { + // do nothing + } +} diff --git a/vendor/topthink/framework/src/think/console/output/formatter/Stack.php b/vendor/topthink/framework/src/think/console/output/formatter/Stack.php new file mode 100644 index 0000000..5366259 --- /dev/null +++ b/vendor/topthink/framework/src/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(): void + { + $this->styles = []; + } + + /** + * 推一个样式进入堆栈 + * @param Style $style + */ + public function push(Style $style): void + { + $this->styles[] = $style; + } + + /** + * 从堆栈中弹出一个样式 + * @param Style|null $style + * @return Style + * @throws \InvalidArgumentException + */ + public function pop(Style $style = null): Style + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + /** + * @var int $index + * @var Style $stackedStyle + */ + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new \InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * 计算堆栈的当前样式。 + * @return Style + */ + public function getCurrent(): Style + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[count($this->styles) - 1]; + } + + /** + * @param Style $emptyStyle + * @return Stack + */ + public function setEmptyStyle(Style $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return Style + */ + public function getEmptyStyle(): Style + { + return $this->emptyStyle; + } +} diff --git a/vendor/topthink/framework/src/think/console/output/formatter/Style.php b/vendor/topthink/framework/src/think/console/output/formatter/Style.php new file mode 100644 index 0000000..2aae768 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/formatter/Style.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Style +{ + protected static $availableForegroundColors = [ + 'black' => ['set' => 30, 'unset' => 39], + 'red' => ['set' => 31, 'unset' => 39], + 'green' => ['set' => 32, 'unset' => 39], + 'yellow' => ['set' => 33, 'unset' => 39], + 'blue' => ['set' => 34, 'unset' => 39], + 'magenta' => ['set' => 35, 'unset' => 39], + 'cyan' => ['set' => 36, 'unset' => 39], + 'white' => ['set' => 37, 'unset' => 39], + ]; + + protected static $availableBackgroundColors = [ + 'black' => ['set' => 40, 'unset' => 49], + 'red' => ['set' => 41, 'unset' => 49], + 'green' => ['set' => 42, 'unset' => 49], + 'yellow' => ['set' => 43, 'unset' => 49], + 'blue' => ['set' => 44, 'unset' => 49], + 'magenta' => ['set' => 45, 'unset' => 49], + 'cyan' => ['set' => 46, 'unset' => 49], + 'white' => ['set' => 47, 'unset' => 49], + ]; + + protected static $availableOptions = [ + 'bold' => ['set' => 1, 'unset' => 22], + 'underscore' => ['set' => 4, 'unset' => 24], + 'blink' => ['set' => 5, 'unset' => 25], + 'reverse' => ['set' => 7, 'unset' => 27], + 'conceal' => ['set' => 8, 'unset' => 28], + ]; + + private $foreground; + private $background; + private $options = []; + + /** + * 初始化输出的样式 + * @param string|null $foreground 字体颜色 + * @param string|null $background 背景色 + * @param array $options 格式 + * @api + */ + public function __construct($foreground = null, $background = null, array $options = []) + { + if (null !== $foreground) { + $this->setForeground($foreground); + } + if (null !== $background) { + $this->setBackground($background); + } + if (count($options)) { + $this->setOptions($options); + } + } + + /** + * 设置字体颜色 + * @param string|null $color 颜色名 + * @throws \InvalidArgumentException + * @api + */ + public function setForeground($color = null) + { + if (null === $color) { + $this->foreground = null; + + return; + } + + if (!isset(static::$availableForegroundColors[$color])) { + throw new \InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors)))); + } + + $this->foreground = static::$availableForegroundColors[$color]; + } + + /** + * 设置背景色 + * @param string|null $color 颜色名 + * @throws \InvalidArgumentException + * @api + */ + public function setBackground($color = null) + { + if (null === $color) { + $this->background = null; + + return; + } + + if (!isset(static::$availableBackgroundColors[$color])) { + throw new \InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors)))); + } + + $this->background = static::$availableBackgroundColors[$color]; + } + + /** + * 设置字体格式 + * @param string $option 格式名 + * @throws \InvalidArgumentException When the option name isn't defined + * @api + */ + public function setOption(string $option): void + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + if (!in_array(static::$availableOptions[$option], $this->options)) { + $this->options[] = static::$availableOptions[$option]; + } + } + + /** + * 重置字体格式 + * @param string $option 格式名 + * @throws \InvalidArgumentException + */ + public function unsetOption(string $option): void + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + $pos = array_search(static::$availableOptions[$option], $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + } + + /** + * 批量设置字体格式 + * @param array $options + */ + public function setOptions(array $options) + { + $this->options = []; + + foreach ($options as $option) { + $this->setOption($option); + } + } + + /** + * 应用样式到文字 + * @param string $text 文字 + * @return string + */ + public function apply(string $text): string + { + $setCodes = []; + $unsetCodes = []; + + if (null !== $this->foreground) { + $setCodes[] = $this->foreground['set']; + $unsetCodes[] = $this->foreground['unset']; + } + if (null !== $this->background) { + $setCodes[] = $this->background['set']; + $unsetCodes[] = $this->background['unset']; + } + if (count($this->options)) { + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + } + + if (0 === count($setCodes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); + } +} diff --git a/vendor/topthink/framework/src/think/console/output/question/Choice.php b/vendor/topthink/framework/src/think/console/output/question/Choice.php new file mode 100644 index 0000000..1da1750 --- /dev/null +++ b/vendor/topthink/framework/src/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(): array + { + return $this->choices; + } + + /** + * 设置可否多选 + * @param bool $multiselect + * @return self + */ + public function setMultiselect(bool $multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + public function isMultiselect(): bool + { + return $this->multiselect; + } + + /** + * 获取提示 + * @return string + */ + public function getPrompt(): string + { + return $this->prompt; + } + + /** + * 设置提示 + * @param string $prompt + * @return self + */ + public function setPrompt(string $prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * 设置错误提示信息 + * @param string $errorMessage + * @return self + */ + public function setErrorMessage(string $errorMessage) + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * 获取默认的验证方法 + * @return callable + */ + private function getDefaultValidator() + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + // Collapse all spaces. + $selectedChoices = str_replace(' ', '', $selected); + + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $selected)); + } + $selectedChoices = explode(',', $selectedChoices); + } else { + $selectedChoices = [$selected]; + } + + $multiselectChoices = []; + foreach ($selectedChoices as $value) { + $results = []; + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (count($results) > 1) { + throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (!empty($result)) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (empty($result) && array_key_exists($value, $choices)) { + $result = $value; + } + + if (false === $result) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + array_push($multiselectChoices, $result); + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/vendor/topthink/framework/src/think/console/output/question/Confirmation.php b/vendor/topthink/framework/src/think/console/output/question/Confirmation.php new file mode 100644 index 0000000..bf71b5d --- /dev/null +++ b/vendor/topthink/framework/src/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(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, (bool) $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * 获取默认的答案回调 + * @return callable + */ + private function getDefaultNormalizer() + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return !$answer || $answerIsTrue; + }; + } +} diff --git a/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php b/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php new file mode 100644 index 0000000..e953f66 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php @@ -0,0 +1,88 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +/** + * 缓存驱动接口 + */ +interface CacheHandlerInterface +{ + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name); + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null); + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null); + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1); + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1); + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function delete($name); + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(); + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys); + +} diff --git a/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php b/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php new file mode 100644 index 0000000..896ac29 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php @@ -0,0 +1,28 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +/** + * 日志驱动接口 + */ +interface LogHandlerInterface +{ + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log): bool; + +} diff --git a/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php b/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php new file mode 100644 index 0000000..49cfa75 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +use Closure; +use think\Collection; +use think\db\Query; +use think\Model; + +/** + * 模型关联接口 + */ +interface ModelRelationInterface +{ + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection; + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包条件 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null): void; + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包条件 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null): void; + + /** + * 关联统计 + * @access public + * @param Model $result 模型对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure, string $aggregate = 'count', string $field = '*', string &$name = null); + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string; + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER'): Query; + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = ''): Query; +} diff --git a/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php b/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php new file mode 100644 index 0000000..caed322 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php @@ -0,0 +1,23 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +/** + * Session驱动接口 + */ +interface SessionHandlerInterface +{ + public function read(string $sessionId): string; + public function delete(string $sessionId): bool; + public function write(string $sessionId, string $data): bool; +} diff --git a/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php b/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php new file mode 100644 index 0000000..f01820d --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +/** + * 视图驱动接口 + */ +interface TemplateHandlerInterface +{ + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists(string $template): bool; + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch(string $template, array $data = []): void; + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display(string $content, array $data = []): void; + + /** + * 配置模板引擎 + * @access private + * @param array $config 参数 + * @return void + */ + public function config(array $config): void; + + /** + * 获取模板引擎配置 + * @access public + * @param string $name 参数名 + * @return void + */ + public function getConfig(string $name); +} diff --git a/vendor/topthink/framework/src/think/event/AppInit.php b/vendor/topthink/framework/src/think/event/AppInit.php new file mode 100644 index 0000000..83d75e7 --- /dev/null +++ b/vendor/topthink/framework/src/think/event/AppInit.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * AppInit事件类 + */ +class AppInit +{} diff --git a/vendor/topthink/framework/src/think/event/HttpEnd.php b/vendor/topthink/framework/src/think/event/HttpEnd.php new file mode 100644 index 0000000..5296ef1 --- /dev/null +++ b/vendor/topthink/framework/src/think/event/HttpEnd.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * HttpEnd事件类 + */ +class HttpEnd +{} diff --git a/vendor/topthink/framework/src/think/event/HttpRun.php b/vendor/topthink/framework/src/think/event/HttpRun.php new file mode 100644 index 0000000..a9cd7c3 --- /dev/null +++ b/vendor/topthink/framework/src/think/event/HttpRun.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * HttpRun事件类 + */ +class HttpRun +{} diff --git a/vendor/topthink/framework/src/think/event/LogWrite.php b/vendor/topthink/framework/src/think/event/LogWrite.php new file mode 100644 index 0000000..470e119 --- /dev/null +++ b/vendor/topthink/framework/src/think/event/LogWrite.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * LogWrite事件类 + */ +class LogWrite +{ + /** @var string */ + public $channel; + + /** @var array */ + public $log; + + public function __construct($channel, $log) + { + $this->channel = $channel; + $this->log = $log; + } +} diff --git a/vendor/topthink/framework/src/think/event/RouteLoaded.php b/vendor/topthink/framework/src/think/event/RouteLoaded.php new file mode 100644 index 0000000..eee1f3e --- /dev/null +++ b/vendor/topthink/framework/src/think/event/RouteLoaded.php @@ -0,0 +1,21 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * 路由加载完成事件 + */ +class RouteLoaded +{ + +} diff --git a/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php b/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php new file mode 100644 index 0000000..2fa1f58 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use Psr\Container\NotFoundExceptionInterface; +use RuntimeException; +use Throwable; + +class ClassNotFoundException extends RuntimeException implements NotFoundExceptionInterface +{ + protected $class; + + public function __construct(string $message, string $class = '', Throwable $previous = null) + { + $this->message = $message; + $this->class = $class; + + parent::__construct($message, 0, $previous); + } + + /** + * 获取类名 + * @access public + * @return string + */ + public function getClass() + { + return $this->class; + } +} diff --git a/vendor/topthink/framework/src/think/exception/ErrorException.php b/vendor/topthink/framework/src/think/exception/ErrorException.php new file mode 100644 index 0000000..54de0fe --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/ErrorException.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +use think\Exception; + +/** + * ThinkPHP错误异常 + * 主要用于封装 set_error_handler 和 register_shutdown_function 得到的错误 + * 除开从 think\Exception 继承的功能 + * 其他和PHP系统\ErrorException功能基本一样 + */ +class ErrorException extends Exception +{ + /** + * 用于保存错误级别 + * @var integer + */ + protected $severity; + + /** + * 错误异常构造函数 + * @access public + * @param integer $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param integer $line 出错行号 + */ + public function __construct(int $severity, string $message, string $file, int $line) + { + $this->severity = $severity; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->code = 0; + } + + /** + * 获取错误级别 + * @access public + * @return integer 错误级别 + */ + final public function getSeverity() + { + return $this->severity; + } +} diff --git a/vendor/topthink/framework/src/think/exception/FileException.php b/vendor/topthink/framework/src/think/exception/FileException.php new file mode 100644 index 0000000..2254472 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/FileException.php @@ -0,0 +1,17 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +class FileException extends \RuntimeException +{ +} diff --git a/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php b/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php new file mode 100644 index 0000000..ee2bcad --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php @@ -0,0 +1,30 @@ +message = $message; + $this->func = $func; + + parent::__construct($message, 0, $previous); + } + + /** + * 获取方法名 + * @access public + * @return string + */ + public function getFunc() + { + return $this->func; + } +} diff --git a/vendor/topthink/framework/src/think/exception/Handle.php b/vendor/topthink/framework/src/think/exception/Handle.php new file mode 100644 index 0000000..9f92054 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/Handle.php @@ -0,0 +1,334 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +use Exception; +use think\App; +use think\console\Output; +use think\db\exception\DataNotFoundException; +use think\db\exception\ModelNotFoundException; +use think\Request; +use think\Response; +use Throwable; + +/** + * 系统异常处理类 + */ +class Handle +{ + /** @var App */ + protected $app; + + protected $ignoreReport = [ + HttpException::class, + HttpResponseException::class, + ModelNotFoundException::class, + DataNotFoundException::class, + ValidateException::class, + ]; + + protected $isJson = false; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * Report or log an exception. + * + * @access public + * @param Throwable $exception + * @return void + */ + public function report(Throwable $exception): void + { + if (!$this->isIgnoreReport($exception)) { + // 收集异常数据 + if ($this->app->isDebug()) { + $data = [ + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'code' => $this->getCode($exception), + ]; + $log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]"; + } else { + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + $log = "[{$data['code']}]{$data['message']}"; + } + + if ($this->app->config->get('log.record_trace')) { + $log .= PHP_EOL . $exception->getTraceAsString(); + } + + try { + $this->app->log->record($log, 'error'); + } catch (Exception $e){} + } + } + + protected function isIgnoreReport(Throwable $exception): bool + { + foreach ($this->ignoreReport as $class) { + if ($exception instanceof $class) { + return true; + } + } + + return false; + } + + /** + * Render an exception into an HTTP response. + * + * @access public + * @param Request $request + * @param Throwable $e + * @return Response + */ + public function render($request, Throwable $e): Response + { + $this->isJson = $request->isJson(); + if ($e instanceof HttpResponseException) { + return $e->getResponse(); + } elseif ($e instanceof HttpException) { + return $this->renderHttpException($e); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @access public + * @param Output $output + * @param Throwable $e + */ + public function renderForConsole(Output $output, Throwable $e): void + { + if ($this->app->isDebug()) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } + + $output->renderException($e); + } + + /** + * @access protected + * @param HttpException $e + * @return Response + */ + protected function renderHttpException(HttpException $e): Response + { + $status = $e->getStatusCode(); + $template = $this->app->config->get('app.http_exception_template'); + + if (!$this->app->isDebug() && !empty($template[$status])) { + return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * 收集异常数据 + * @param Throwable $exception + * @return array + */ + protected function convertExceptionToArray(Throwable $exception): array + { + if ($this->app->isDebug()) { + // 调试模式,获取详细的错误信息 + $traces = []; + $nextException = $exception; + do { + $traces[] = [ + 'name' => get_class($nextException), + 'file' => $nextException->getFile(), + 'line' => $nextException->getLine(), + 'code' => $this->getCode($nextException), + 'message' => $this->getMessage($nextException), + 'trace' => $nextException->getTrace(), + 'source' => $this->getSourceCode($nextException), + ]; + } while ($nextException = $nextException->getPrevious()); + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + 'traces' => $traces, + 'datas' => $this->getExtendData($exception), + 'tables' => [ + 'GET Data' => $this->app->request->get(), + 'POST Data' => $this->app->request->post(), + 'Files' => $this->app->request->file(), + 'Cookies' => $this->app->request->cookie(), + 'Session' => $this->app->session->all(), + 'Server/Request Data' => $this->app->request->server(), + 'Environment Variables' => $this->app->request->env(), + 'ThinkPHP Constants' => $this->getConst(), + ], + ]; + } else { + // 部署模式仅显示 Code 和 Message + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + + if (!$this->app->config->get('app.show_error_msg')) { + // 不显示详细错误信息 + $data['message'] = $this->app->config->get('app.error_message'); + } + } + + return $data; + } + + /** + * @access protected + * @param Throwable $exception + * @return Response + */ + protected function convertExceptionToResponse(Throwable $exception): Response + { + if (!$this->isJson) { + $response = Response::create($this->renderExceptionContent($exception)); + } else { + $response = Response::create($this->convertExceptionToArray($exception), 'json'); + } + + if ($exception instanceof HttpException) { + $statusCode = $exception->getStatusCode(); + $response->header($exception->getHeaders()); + } + + return $response->code($statusCode ?? 500); + } + + protected function renderExceptionContent(Throwable $exception): string + { + ob_start(); + $data = $this->convertExceptionToArray($exception); + extract($data); + include $this->app->config->get('app.exception_tmpl') ?: __DIR__ . '/../../tpl/think_exception.tpl'; + + return ob_get_clean(); + } + + /** + * 获取错误编码 + * ErrorException则使用错误级别作为错误编码 + * @access protected + * @param Throwable $exception + * @return integer 错误编码 + */ + protected function getCode(Throwable $exception) + { + $code = $exception->getCode(); + + if (!$code && $exception instanceof ErrorException) { + $code = $exception->getSeverity(); + } + + return $code; + } + + /** + * 获取错误信息 + * ErrorException则使用错误级别作为错误编码 + * @access protected + * @param Throwable $exception + * @return string 错误信息 + */ + protected function getMessage(Throwable $exception): string + { + $message = $exception->getMessage(); + + if ($this->app->runningInConsole()) { + return $message; + } + + $lang = $this->app->lang; + + if (strpos($message, ':')) { + $name = strstr($message, ':', true); + $message = $lang->has($name) ? $lang->get($name) . strstr($message, ':') : $message; + } elseif (strpos($message, ',')) { + $name = strstr($message, ',', true); + $message = $lang->has($name) ? $lang->get($name) . ':' . substr(strstr($message, ','), 1) : $message; + } elseif ($lang->has($message)) { + $message = $lang->get($message); + } + + return $message; + } + + /** + * 获取出错文件内容 + * 获取错误的前9行和后9行 + * @access protected + * @param Throwable $exception + * @return array 错误文件内容 + */ + protected function getSourceCode(Throwable $exception): array + { + // 读取前9行和后9行 + $line = $exception->getLine(); + $first = ($line - 9 > 0) ? $line - 9 : 1; + + try { + $contents = file($exception->getFile()) ?: []; + $source = [ + 'first' => $first, + 'source' => array_slice($contents, $first - 1, 19), + ]; + } catch (Exception $e) { + $source = []; + } + + return $source; + } + + /** + * 获取异常扩展信息 + * 用于非调试模式html返回类型显示 + * @access protected + * @param Throwable $exception + * @return array 异常类定义的扩展数据 + */ + protected function getExtendData(Throwable $exception): array + { + $data = []; + + if ($exception instanceof \think\Exception) { + $data = $exception->getData(); + } + + return $data; + } + + /** + * 获取常量列表 + * @access protected + * @return array 常量列表 + */ + protected function getConst(): array + { + $const = get_defined_constants(true); + + return $const['user'] ?? []; + } +} diff --git a/vendor/topthink/framework/src/think/exception/HttpException.php b/vendor/topthink/framework/src/think/exception/HttpException.php new file mode 100644 index 0000000..74fabfc --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/HttpException.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +use Exception; + +/** + * HTTP异常 + */ +class HttpException extends \RuntimeException +{ + private $statusCode; + private $headers; + + public function __construct(int $statusCode, string $message = '', Exception $previous = null, array $headers = [], $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message, $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/topthink/framework/src/think/exception/HttpResponseException.php b/vendor/topthink/framework/src/think/exception/HttpResponseException.php new file mode 100644 index 0000000..759254c --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/HttpResponseException.php @@ -0,0 +1,37 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +use think\Response; + +/** + * HTTP响应异常 + */ +class HttpResponseException extends \RuntimeException +{ + /** + * @var Response + */ + protected $response; + + public function __construct(Response $response) + { + $this->response = $response; + } + + public function getResponse() + { + return $this->response; + } + +} diff --git a/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php b/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php new file mode 100644 index 0000000..d317278 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\exception; + +use Psr\Cache\InvalidArgumentException as Psr6CacheInvalidArgumentInterface; +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface; + +/** + * 非法数据异常 + */ +class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInvalidArgumentInterface, SimpleCacheInvalidArgumentInterface +{ +} diff --git a/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php b/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php new file mode 100644 index 0000000..f50dff6 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +/** + * 路由未定义异常 + */ +class RouteNotFoundException extends HttpException +{ + + public function __construct() + { + parent::__construct(404, 'Route Not Found'); + } + +} diff --git a/vendor/topthink/framework/src/think/exception/ValidateException.php b/vendor/topthink/framework/src/think/exception/ValidateException.php new file mode 100644 index 0000000..cc79e19 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/ValidateException.php @@ -0,0 +1,37 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +/** + * 数据验证异常 + */ +class ValidateException extends \RuntimeException +{ + protected $error; + + public function __construct($error) + { + $this->error = $error; + $this->message = is_array($error) ? implode(PHP_EOL, $error) : $error; + } + + /** + * 获取验证错误信息 + * @access public + * @return array|string + */ + public function getError() + { + return $this->error; + } +} diff --git a/vendor/topthink/framework/src/think/facade/App.php b/vendor/topthink/framework/src/think/facade/App.php new file mode 100644 index 0000000..4f64d96 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/App.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\App + * @package think\facade + * @mixin \think\App + */ +class App extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'app'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Cache.php b/vendor/topthink/framework/src/think/facade/Cache.php new file mode 100644 index 0000000..62391b7 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Cache.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Cache + * @package think\facade + * @mixin \think\Cache + */ +class Cache extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'cache'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Config.php b/vendor/topthink/framework/src/think/facade/Config.php new file mode 100644 index 0000000..93916e4 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Config.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Config + * @package think\facade + * @mixin \think\Config + */ +class Config extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'config'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Console.php b/vendor/topthink/framework/src/think/facade/Console.php new file mode 100644 index 0000000..f3f9239 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Console.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * Class Console + * @package think\facade + * @mixin \think\Console + */ +class Console extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'console'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Cookie.php b/vendor/topthink/framework/src/think/facade/Cookie.php new file mode 100644 index 0000000..98aa6c2 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Cookie.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Cookie + * @package think\facade + * @mixin \think\Cookie + */ +class Cookie extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'cookie'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Env.php b/vendor/topthink/framework/src/think/facade/Env.php new file mode 100644 index 0000000..5452e90 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Env.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Env + * @package think\facade + * @mixin \think\Env + */ +class Env extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'env'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Event.php b/vendor/topthink/framework/src/think/facade/Event.php new file mode 100644 index 0000000..0934452 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Event.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Event + * @package think\facade + * @mixin \think\Event + */ +class Event extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'event'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Filesystem.php b/vendor/topthink/framework/src/think/facade/Filesystem.php new file mode 100644 index 0000000..6fe4d5a --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Filesystem.php @@ -0,0 +1,28 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * Class Filesystem + * @package think\facade + * @mixin \think\Filesystem + */ +class Filesystem extends Facade +{ + protected static function getFacadeClass() + { + return 'filesystem'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Lang.php b/vendor/topthink/framework/src/think/facade/Lang.php new file mode 100644 index 0000000..1085c15 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Lang.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Lang + * @package think\facade + * @mixin \think\Lang + */ +class Lang extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'lang'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Log.php b/vendor/topthink/framework/src/think/facade/Log.php new file mode 100644 index 0000000..e92a5dc --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Log.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Log + * @package think\facade + * @mixin \think\Log + */ +class Log extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'log'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Middleware.php b/vendor/topthink/framework/src/think/facade/Middleware.php new file mode 100644 index 0000000..8a9cc36 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Middleware.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Middleware + * @package think\facade + * @mixin \think\Middleware + */ +class Middleware extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'middleware'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Request.php b/vendor/topthink/framework/src/think/facade/Request.php new file mode 100644 index 0000000..8bf5fc2 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Request.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Request + * @package think\facade + * @mixin \think\Request + */ +class Request extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'request'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Route.php b/vendor/topthink/framework/src/think/facade/Route.php new file mode 100644 index 0000000..5fd5e40 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Route.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Route + * @package think\facade + * @mixin \think\Route + */ +class Route extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'route'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Session.php b/vendor/topthink/framework/src/think/facade/Session.php new file mode 100644 index 0000000..4bcb6e8 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Session.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Session + * @package think\facade + * @mixin \think\Session + */ +class Session extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'session'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Validate.php b/vendor/topthink/framework/src/think/facade/Validate.php new file mode 100644 index 0000000..0d6a34e --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Validate.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Validate + * @package think\facade + * @mixin \think\Validate + */ +class Validate extends Facade +{ + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance = true; + + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'validate'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/View.php b/vendor/topthink/framework/src/think/facade/View.php new file mode 100644 index 0000000..0fecb15 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/View.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\View + * @package think\facade + * @mixin \think\View + */ +class View extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'view'; + } +} diff --git a/vendor/topthink/framework/src/think/file/UploadedFile.php b/vendor/topthink/framework/src/think/file/UploadedFile.php new file mode 100644 index 0000000..7810eac --- /dev/null +++ b/vendor/topthink/framework/src/think/file/UploadedFile.php @@ -0,0 +1,143 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\file; + +use think\exception\FileException; +use think\File; + +class UploadedFile extends File +{ + + private $test = false; + private $originalName; + private $mimeType; + private $error; + + public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false) + { + $this->originalName = $originalName; + $this->mimeType = $mimeType ?: 'application/octet-stream'; + $this->test = $test; + $this->error = $error ?: UPLOAD_ERR_OK; + + parent::__construct($path, UPLOAD_ERR_OK === $this->error); + } + + public function isValid(): bool + { + $isOk = UPLOAD_ERR_OK === $this->error; + + return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname()); + } + + /** + * 上传文件 + * @access public + * @param string $directory 保存路径 + * @param string|null $name 保存的文件名 + * @return File + */ + public function move(string $directory, string $name = null): File + { + if ($this->isValid()) { + if ($this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { + $error = $msg; + }); + + $moved = move_uploaded_file($this->getPathname(), $target); + restore_error_handler(); + if (!$moved) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + throw new FileException($this->getErrorMessage()); + } + + /** + * 获取错误信息 + * @access public + * @return string + */ + protected function getErrorMessage(): string + { + switch ($this->error) { + case 1: + case 2: + $message = 'upload File size exceeds the maximum value'; + break; + case 3: + $message = 'only the portion of file is uploaded'; + break; + case 4: + $message = 'no file to uploaded'; + break; + case 6: + $message = 'upload temp dir not found'; + break; + case 7: + $message = 'file write error'; + break; + default: + $message = 'unknown upload error'; + } + + return $message; + } + + /** + * 获取上传文件类型信息 + * @return string + */ + public function getOriginalMime(): string + { + return $this->mimeType; + } + + /** + * 上传文件名 + * @return string + */ + public function getOriginalName(): string + { + return $this->originalName; + } + + /** + * 获取上传文件扩展名 + * @return string + */ + public function getOriginalExtension(): string + { + return pathinfo($this->originalName, PATHINFO_EXTENSION); + } + + /** + * 获取文件扩展名 + * @return string + */ + public function extension(): string + { + return $this->getOriginalExtension(); + } +} diff --git a/vendor/topthink/framework/src/think/filesystem/CacheStore.php b/vendor/topthink/framework/src/think/filesystem/CacheStore.php new file mode 100644 index 0000000..46659ba --- /dev/null +++ b/vendor/topthink/framework/src/think/filesystem/CacheStore.php @@ -0,0 +1,54 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\filesystem; + +use League\Flysystem\Cached\Storage\AbstractCache; +use Psr\SimpleCache\CacheInterface; + +class CacheStore extends AbstractCache +{ + protected $store; + + protected $key; + + protected $expire; + + public function __construct(CacheInterface $store, $key = 'flysystem', $expire = null) + { + $this->key = $key; + $this->store = $store; + $this->expire = $expire; + } + + /** + * Store the cache. + */ + public function save() + { + $contents = $this->getForStorage(); + + $this->store->set($this->key, $contents, $this->expire); + } + + /** + * Load the cache. + */ + public function load() + { + $contents = $this->store->get($this->key); + + if (!is_null($contents)) { + $this->setFromStorage($contents); + } + } +} diff --git a/vendor/topthink/framework/src/think/filesystem/Driver.php b/vendor/topthink/framework/src/think/filesystem/Driver.php new file mode 100644 index 0000000..26826ad --- /dev/null +++ b/vendor/topthink/framework/src/think/filesystem/Driver.php @@ -0,0 +1,133 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\filesystem; + +use League\Flysystem\AdapterInterface; +use League\Flysystem\Adapter\AbstractAdapter; +use League\Flysystem\Cached\CachedAdapter; +use League\Flysystem\Cached\Storage\Memory as MemoryStore; +use League\Flysystem\Filesystem; +use think\Cache; +use think\File; + +/** + * Class Driver + * @package think\filesystem + * @mixin Filesystem + */ +abstract class Driver +{ + + /** @var Cache */ + protected $cache; + + /** @var Filesystem */ + protected $filesystem; + + /** + * 配置参数 + * @var array + */ + protected $config = []; + + public function __construct(Cache $cache, array $config) + { + $this->cache = $cache; + $this->config = array_merge($this->config, $config); + + $adapter = $this->createAdapter(); + $this->filesystem = $this->createFilesystem($adapter); + } + + protected function createCacheStore($config) + { + if (true === $config) { + return new MemoryStore; + } + + return new CacheStore( + $this->cache->store($config['store']), + $config['prefix'] ?? 'flysystem', + $config['expire'] ?? null + ); + } + + abstract protected function createAdapter(): AdapterInterface; + + protected function createFilesystem(AdapterInterface $adapter): Filesystem + { + if (!empty($this->config['cache'])) { + $adapter = new CachedAdapter($adapter, $this->createCacheStore($this->config['cache'])); + } + + $config = array_intersect_key($this->config, array_flip(['visibility', 'disable_asserts', 'url'])); + + return new Filesystem($adapter, count($config) > 0 ? $config : null); + } + + /** + * 获取文件完整路径 + * @param string $path + * @return string + */ + public function path(string $path): string + { + $adapter = $this->filesystem->getAdapter(); + + if ($adapter instanceof AbstractAdapter) { + return $adapter->applyPathPrefix($path); + } + + return $path; + } + + /** + * 保存文件 + * @param string $path 路径 + * @param File $file 文件 + * @param null|string|\Closure $rule 文件名规则 + * @param array $options 参数 + * @return bool|string + */ + public function putFile(string $path, File $file, $rule = null, array $options = []) + { + return $this->putFileAs($path, $file, $file->hashName($rule), $options); + } + + /** + * 指定文件名保存文件 + * @param string $path 路径 + * @param File $file 文件 + * @param string $name 文件名 + * @param array $options 参数 + * @return bool|string + */ + public function putFileAs(string $path, File $file, string $name, array $options = []) + { + $stream = fopen($file->getRealPath(), 'r'); + $path = trim($path . '/' . $name, '/'); + + $result = $this->putStream($path, $stream, $options); + + if (is_resource($stream)) { + fclose($stream); + } + + return $result ? $path : false; + } + + public function __call($method, $parameters) + { + return $this->filesystem->$method(...$parameters); + } +} diff --git a/vendor/topthink/framework/src/think/filesystem/driver/Local.php b/vendor/topthink/framework/src/think/filesystem/driver/Local.php new file mode 100644 index 0000000..60aa71c --- /dev/null +++ b/vendor/topthink/framework/src/think/filesystem/driver/Local.php @@ -0,0 +1,41 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\filesystem\driver; + +use League\Flysystem\AdapterInterface; +use League\Flysystem\Adapter\Local as LocalAdapter; +use think\filesystem\Driver; + +class Local extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + 'root' => '', + ]; + + protected function createAdapter(): AdapterInterface + { + $permissions = $this->config['permissions'] ?? []; + + $links = ($this->config['links'] ?? null) === 'skip' + ? LocalAdapter::SKIP_LINKS + : LocalAdapter::DISALLOW_LINKS; + + return new LocalAdapter( + $this->config['root'], LOCK_EX, $links, $permissions + ); + } +} diff --git a/vendor/topthink/framework/src/think/initializer/BootService.php b/vendor/topthink/framework/src/think/initializer/BootService.php new file mode 100644 index 0000000..ef9b25e --- /dev/null +++ b/vendor/topthink/framework/src/think/initializer/BootService.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\initializer; + +use think\App; + +/** + * 启动系统服务 + */ +class BootService +{ + public function init(App $app) + { + $app->boot(); + } +} diff --git a/vendor/topthink/framework/src/think/initializer/Error.php b/vendor/topthink/framework/src/think/initializer/Error.php new file mode 100644 index 0000000..27fef64 --- /dev/null +++ b/vendor/topthink/framework/src/think/initializer/Error.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\initializer; + +use think\App; +use think\console\Output as ConsoleOutput; +use think\exception\ErrorException; +use think\exception\Handle; +use Throwable; + +/** + * 错误和异常处理 + */ +class Error +{ + /** @var App */ + protected $app; + + /** + * 注册异常处理 + * @access public + * @param App $app + * @return void + */ + public function init(App $app) + { + $this->app = $app; + error_reporting(E_ALL); + set_error_handler([$this, 'appError']); + set_exception_handler([$this, 'appException']); + register_shutdown_function([$this, 'appShutdown']); + } + + /** + * Exception Handler + * @access public + * @param \Throwable $e + */ + public function appException(Throwable $e): void + { + $handler = $this->getExceptionHandler(); + + $handler->report($e); + + if ($this->app->runningInConsole()) { + $handler->renderForConsole(new ConsoleOutput, $e); + } else { + $handler->render($this->app->request, $e)->send(); + } + } + + /** + * Error Handler + * @access public + * @param integer $errno 错误编号 + * @param string $errstr 详细错误信息 + * @param string $errfile 出错的文件 + * @param integer $errline 出错行号 + * @throws ErrorException + */ + public function appError(int $errno, string $errstr, string $errfile = '', int $errline = 0): void + { + $exception = new ErrorException($errno, $errstr, $errfile, $errline); + + if (error_reporting() & $errno) { + // 将错误信息托管至 think\exception\ErrorException + throw $exception; + } + } + + /** + * Shutdown Handler + * @access public + */ + public function appShutdown(): void + { + if (!is_null($error = error_get_last()) && $this->isFatal($error['type'])) { + // 将错误信息托管至think\ErrorException + $exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']); + + $this->appException($exception); + } + } + + /** + * 确定错误类型是否致命 + * + * @access protected + * @param int $type + * @return bool + */ + protected function isFatal(int $type): bool + { + return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); + } + + /** + * Get an instance of the exception handler. + * + * @access protected + * @return Handle + */ + protected function getExceptionHandler() + { + return $this->app->make(Handle::class); + } +} diff --git a/vendor/topthink/framework/src/think/initializer/RegisterService.php b/vendor/topthink/framework/src/think/initializer/RegisterService.php new file mode 100644 index 0000000..c63ab35 --- /dev/null +++ b/vendor/topthink/framework/src/think/initializer/RegisterService.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\initializer; + +use think\App; +use think\service\ModelService; +use think\service\PaginatorService; +use think\service\ValidateService; + +/** + * 注册系统服务 + */ +class RegisterService +{ + + protected $services = [ + PaginatorService::class, + ValidateService::class, + ModelService::class, + ]; + + public function init(App $app) + { + $file = $app->getRootPath() . 'vendor/services.php'; + + $services = $this->services; + + if (is_file($file)) { + $services = array_merge($services, include $file); + } + + foreach ($services as $service) { + if (class_exists($service)) { + $app->register($service); + } + } + } +} diff --git a/vendor/topthink/framework/src/think/log/Channel.php b/vendor/topthink/framework/src/think/log/Channel.php new file mode 100644 index 0000000..2660a60 --- /dev/null +++ b/vendor/topthink/framework/src/think/log/Channel.php @@ -0,0 +1,282 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\log; + +use Psr\Log\LoggerInterface; +use think\contract\LogHandlerInterface; +use think\Event; +use think\event\LogWrite; + +class Channel implements LoggerInterface +{ + protected $name; + protected $logger; + protected $event; + + protected $lazy = true; + /** + * 日志信息 + * @var array + */ + protected $log = []; + + /** + * 关闭日志 + * @var array + */ + protected $close = false; + + /** + * 允许写入类型 + * @var array + */ + protected $allow = []; + + public function __construct(string $name, LogHandlerInterface $logger, array $allow, bool $lazy = true, Event $event = null) + { + $this->name = $name; + $this->logger = $logger; + $this->allow = $allow; + $this->lazy = $lazy; + $this->event = $event; + } + + /** + * 关闭通道 + */ + public function close() + { + $this->clear(); + $this->close = true; + } + + /** + * 清空日志 + */ + public function clear() + { + $this->log = []; + } + + /** + * 记录日志信息 + * @access public + * @param mixed $msg 日志信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @param bool $lazy + * @return $this + */ + public function record($msg, string $type = 'info', array $context = [], bool $lazy = true) + { + if ($this->close || (!empty($this->allow) && !in_array($type, $this->allow))) { + return $this; + } + + if (is_string($msg) && !empty($context)) { + $replace = []; + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + $msg = strtr($msg, $replace); + } + + if (!empty($msg) || 0 === $msg) { + $this->log[$type][] = $msg; + } + + if (!$this->lazy || !$lazy) { + $this->save(); + } + + return $this; + } + + /** + * 实时写入日志信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @return $this + */ + public function write($msg, string $type = 'info', array $context = []) + { + return $this->record($msg, $type, $context, false); + } + + /** + * 获取日志信息 + * @return array + */ + public function getLog(): array + { + return $this->log; + } + + /** + * 保存日志 + * @return bool + */ + public function save(): bool + { + $log = $this->log; + if ($this->event) { + $event = new LogWrite($this->name, $log); + $this->event->trigger($event); + $log = $event->log; + } + + if ($this->logger->save($log)) { + $this->clear(); + return true; + } + + return false; + } + + /** + * System is unusable. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function emergency($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + */ + public function log($level, $message, array $context = []) + { + $this->record($message, $level, $context); + } + + public function __call($method, $parameters) + { + $this->log($method, ...$parameters); + } +} diff --git a/vendor/topthink/framework/src/think/log/ChannelSet.php b/vendor/topthink/framework/src/think/log/ChannelSet.php new file mode 100644 index 0000000..e38811c --- /dev/null +++ b/vendor/topthink/framework/src/think/log/ChannelSet.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\log; + +use think\Log; + +/** + * Class ChannelSet + * @package think\log + * @mixin Channel + */ +class ChannelSet +{ + protected $log; + protected $channels; + + public function __construct(Log $log, array $channels) + { + $this->log = $log; + $this->channels = $channels; + } + + public function __call($method, $arguments) + { + foreach ($this->channels as $channel) { + $this->log->channel($channel)->{$method}(...$arguments); + } + } +} diff --git a/vendor/topthink/framework/src/think/log/driver/File.php b/vendor/topthink/framework/src/think/log/driver/File.php new file mode 100644 index 0000000..1b6314d --- /dev/null +++ b/vendor/topthink/framework/src/think/log/driver/File.php @@ -0,0 +1,205 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\log\driver; + +use think\App; +use think\contract\LogHandlerInterface; + +/** + * 本地化调试输出到文件 + */ +class File implements LogHandlerInterface +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + 'time_format' => 'c', + 'single' => false, + 'file_size' => 2097152, + 'path' => '', + 'apart_level' => [], + 'max_files' => 0, + 'json' => false, + 'json_options' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, + 'format' => '[%s][%s] %s', + ]; + + // 实例化并传入参数 + public function __construct(App $app, $config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + + if (empty($this->config['format'])) { + $this->config['format'] = '[%s][%s] %s'; + } + + if (empty($this->config['path'])) { + $this->config['path'] = $app->getRuntimePath() . 'log'; + } + + if (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) { + $this->config['path'] .= DIRECTORY_SEPARATOR; + } + } + + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log): bool + { + $destination = $this->getMasterLogFile(); + + $path = dirname($destination); + !is_dir($path) && mkdir($path, 0755, true); + + $info = []; + + // 日志信息封装 + $time = date($this->config['time_format']); + + foreach ($log as $type => $val) { + $message = []; + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + + $message[] = $this->config['json'] ? + json_encode(['time' => $time, 'type' => $type, 'msg' => $msg], $this->config['json_options']) : + sprintf($this->config['format'], $time, $type, $msg); + } + + if (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level'])) { + // 独立记录的日志级别 + $filename = $this->getApartLevelFile($path, $type); + $this->write($message, $filename); + continue; + } + + $info[$type] = $message; + } + + if ($info) { + return $this->write($info, $destination); + } + + return true; + } + + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @return bool + */ + protected function write(array $message, string $destination): bool + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); + + $info = []; + + foreach ($message as $type => $msg) { + $info[$type] = is_array($msg) ? implode(PHP_EOL, $msg) : $msg; + } + + $message = implode(PHP_EOL, $info) . PHP_EOL; + + return error_log($message, 3, $destination); + } + + /** + * 获取主日志文件名 + * @access public + * @return string + */ + protected function getMasterLogFile(): string + { + + if ($this->config['max_files']) { + $files = glob($this->config['path'] . '*.log'); + + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { + // + } + } + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + $destination = $this->config['path'] . $name . '.log'; + } else { + + if ($this->config['max_files']) { + $filename = date('Ymd') . '.log'; + } else { + $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . '.log'; + } + + $destination = $this->config['path'] . $filename; + } + + return $destination; + } + + /** + * 获取独立日志文件名 + * @access public + * @param string $path 日志目录 + * @param string $type 日志类型 + * @return string + */ + protected function getApartLevelFile(string $path, string $type): string + { + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + + $name .= '_' . $type; + } elseif ($this->config['max_files']) { + $name = date('Ymd') . '_' . $type; + } else { + $name = date('d') . '_' . $type; + } + + return $path . DIRECTORY_SEPARATOR . $name . '.log'; + } + + /** + * 检查日志文件大小并自动生成备份文件 + * @access protected + * @param string $destination 日志文件 + * @return void + */ + protected function checkLogSize(string $destination): void + { + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + try { + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); + } catch (\Exception $e) { + // + } + } + } +} diff --git a/vendor/topthink/framework/src/think/log/driver/Socket.php b/vendor/topthink/framework/src/think/log/driver/Socket.php new file mode 100644 index 0000000..fc74258 --- /dev/null +++ b/vendor/topthink/framework/src/think/log/driver/Socket.php @@ -0,0 +1,306 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\log\driver; + +use Psr\Container\NotFoundExceptionInterface; +use think\App; +use think\contract\LogHandlerInterface; + +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +class Socket implements LogHandlerInterface +{ + protected $app; + + protected $config = [ + // socket服务器地址 + 'host' => 'localhost', + // socket服务器端口 + 'port' => 1116, + // 是否显示加载的文件列表 + 'show_included_files' => false, + // 日志强制记录到配置的client_id + 'force_client_ids' => [], + // 限制允许读取日志的client_id + 'allow_client_ids' => [], + // 调试开关 + 'debug' => false, + // 输出到浏览器时默认展开的日志级别 + 'expand_level' => ['debug'], + // 日志头渲染回调 + 'format_head' => null, + ]; + + protected $css = [ + 'sql' => 'color:#009bb4;', + 'sql_warn' => 'color:#009bb4;font-size:14px;', + 'error' => 'color:#f4006b;font-size:14px;', + 'page' => 'color:#40e2ff;background:#171717;', + 'big' => 'font-size:20px;color:red;', + ]; + + protected $allowForceClientIds = []; //配置强制推送且被授权的client_id + + protected $clientArg = []; + + /** + * 架构函数 + * @access public + * @param App $app + * @param array $config 缓存参数 + */ + public function __construct(App $app, array $config = []) + { + $this->app = $app; + + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + + if (!isset($config['debug'])) { + $this->config['debug'] = $app->isDebug(); + } + } + + /** + * 调试输出接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = []): bool + { + if (!$this->check()) { + return false; + } + + $trace = []; + + if ($this->config['debug']) { + if ($this->app->exists('request')) { + $current_uri = $this->app->request->url(true); + } else { + $current_uri = 'cmd:' . implode(' ', $_SERVER['argv'] ?? []); + } + + if (!empty($this->config['format_head'])) { + try { + $current_uri = $this->app->invoke($this->config['format_head'], [$current_uri]); + } catch (NotFoundExceptionInterface $notFoundException) { + // Ignore exception + } + } + + // 基本信息 + $trace[] = [ + 'type' => 'group', + 'msg' => $current_uri, + 'css' => $this->css['page'], + ]; + } + + $expand_level = array_flip($this->config['expand_level']); + + foreach ($log as $type => $val) { + $trace[] = [ + 'type' => isset($expand_level[$type]) ? 'group' : 'groupCollapsed', + 'msg' => '[ ' . $type . ' ]', + 'css' => $this->css[$type] ?? '', + ]; + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + $trace[] = [ + 'type' => 'log', + 'msg' => $msg, + 'css' => '', + ]; + } + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + if ($this->config['show_included_files']) { + $trace[] = [ + 'type' => 'groupCollapsed', + 'msg' => '[ file ]', + 'css' => '', + ]; + + $trace[] = [ + 'type' => 'log', + 'msg' => implode("\n", get_included_files()), + 'css' => '', + ]; + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + + $tabid = $this->getClientArg('tabid'); + + if (!$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; + } + + /** + * 发送给指定客户端 + * @access protected + * @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, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR); + $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 + + $this->send($this->config['host'], $this->config['port'], $msg, $address); + } + + /** + * 检测客户授权 + * @access protected + * @return bool + */ + protected function check() + { + $tabid = $this->getClientArg('tabid'); + + //是否记录日志的检查 + if (!$tabid && !$this->config['force_client_ids']) { + return false; + } + + //用户认证 + $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; + } + + /** + * 获取客户参数 + * @access protected + * @param string $name + * @return string + */ + protected function getClientArg(string $name) + { + if (!$this->app->exists('request')) { + return ''; + } + + if (empty($this->clientArg)) { + if (empty($socketLog = $this->app->request->header('socketlog'))) { + if (empty($socketLog = $this->app->request->header('User-Agent'))) { + return ''; + } + } + + if (!preg_match('/SocketLog\((.*?)\)/', $socketLog, $match)) { + $this->clientArg = ['tabid' => null, 'client_id' => null]; + return ''; + } + parse_str($match[1] ?? '', $this->clientArg); + } + + if (isset($this->clientArg[$name])) { + return $this->clientArg[$name]; + } + + return ''; + } + + /** + * @access protected + * @param string $host - $host of socket server + * @param int $port - $port of socket server + * @param string $message - 发送的消息 + * @param string $address - 地址 + * @return bool + */ + protected function send($host, $port, $message = '', $address = '/') + { + $url = 'http://' . $host . ':' . $port . $address; + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $message); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 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/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php b/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php new file mode 100644 index 0000000..1c1d4c7 --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\Config; +use think\Request; +use think\Response; + +/** + * 跨域请求支持 + */ +class AllowCrossDomain +{ + protected $cookieDomain; + + protected $header = [ + 'Access-Control-Allow-Credentials' => 'true', + 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With', + ]; + + public function __construct(Config $config) + { + $this->cookieDomain = $config->get('cookie.domain', ''); + } + + /** + * 允许跨域请求 + * @access public + * @param Request $request + * @param Closure $next + * @param array $header + * @return Response + */ + public function handle($request, Closure $next, ?array $header = []) + { + $header = !empty($header) ? array_merge($this->header, $header) : $this->header; + + if (!isset($header['Access-Control-Allow-Origin'])) { + $origin = $request->header('origin'); + + if ($origin && ('' == $this->cookieDomain || strpos($origin, $this->cookieDomain))) { + $header['Access-Control-Allow-Origin'] = $origin; + } else { + $header['Access-Control-Allow-Origin'] = '*'; + } + } + + if ($request->method(true) == 'OPTIONS') { + return Response::create()->code(204)->header($header); + } + + return $next($request)->header($header); + } +} diff --git a/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php b/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php new file mode 100644 index 0000000..f2d8a62 --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php @@ -0,0 +1,163 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\Cache; +use think\Config; +use think\Request; +use think\Response; + +/** + * 请求缓存处理 + */ +class CheckRequestCache +{ + /** + * 缓存对象 + * @var Cache + */ + protected $cache; + + /** + * 配置参数 + * @var array + */ + protected $config = [ + // 请求缓存规则 true为自动规则 + 'request_cache_key' => true, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + // 请求缓存的Tag + 'request_cache_tag' => '', + ]; + + public function __construct(Cache $cache, Config $config) + { + $this->cache = $cache; + $this->config = array_merge($this->config, $config->get('route')); + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param Request $request + * @param Closure $next + * @param mixed $cache + * @return Response + */ + public function handle($request, Closure $next, $cache = null) + { + if ($request->isGet() && false !== $cache) { + $cache = $cache ?: $this->getRequestCache($request); + + if ($cache) { + if (is_array($cache)) { + [$key, $expire, $tag] = $cache; + } else { + $key = str_replace('|', '/', $request->url()); + $expire = $cache; + $tag = null; + } + + if (strtotime($request->server('HTTP_IF_MODIFIED_SINCE', '')) + $expire > $request->server('REQUEST_TIME')) { + // 读取缓存 + return Response::create()->code(304); + } elseif (($hit = $this->cache->get($key)) !== null) { + [$content, $header, $when] = $hit; + if (null === $expire || $when + $expire > $request->server('REQUEST_TIME')) { + return Response::create($content)->header($header); + } + } + } + } + + $response = $next($request); + + if (isset($key) && 200 == $response->getCode() && $response->isAllowCache()) { + $header = $response->getHeader(); + $header['Cache-Control'] = 'max-age=' . $expire . ',must-revalidate'; + $header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT'; + $header['Expires'] = gmdate('D, d M Y H:i:s', time() + $expire) . ' GMT'; + + $this->cache->tag($tag)->set($key, [$response->getContent(), $header, time()], $expire); + } + + return $response; + } + + /** + * 读取当前地址的请求缓存信息 + * @access protected + * @param Request $request + * @return mixed + */ + protected function getRequestCache($request) + { + $key = $this->config['request_cache_key']; + $expire = $this->config['request_cache_expire']; + $except = $this->config['request_cache_except']; + $tag = $this->config['request_cache_tag']; + + if ($key instanceof \Closure) { + $key = call_user_func($key, $request); + } + + if (false === $key) { + // 关闭当前缓存 + return; + } + + foreach ($except as $rule) { + if (0 === stripos($request->url(), $rule)) { + return; + } + } + + if (true === $key) { + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + [$key, $fun] = explode('|', $key); + } + + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__CONTROLLER__', '__ACTION__', '__URL__'], [$request->controller(), $request->action(), md5($request->url(true))], $key); + } + + if (false !== strpos($key, ':')) { + $param = $request->param(); + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, $val, $key); + } + } + } elseif (strpos($key, ']')) { + if ('[' . $request->ext() . ']' == $key) { + // 缓存某个后缀的请求 + $key = md5($request->url()); + } else { + return; + } + } + + if (isset($fun)) { + $key = $fun($key); + } + + return [$key, $expire, $tag]; + } +} diff --git a/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php b/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php new file mode 100644 index 0000000..f507903 --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php @@ -0,0 +1,45 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\exception\ValidateException; +use think\Request; +use think\Response; + +/** + * 表单令牌支持 + */ +class FormTokenCheck +{ + + /** + * 表单令牌检测 + * @access public + * @param Request $request + * @param Closure $next + * @param string $token 表单令牌Token名称 + * @return Response + */ + public function handle(Request $request, Closure $next, string $token = null) + { + $check = $request->checkToken($token ?: '__token__'); + + if (false === $check) { + throw new ValidateException('invalid token'); + } + + return $next($request); + } + +} diff --git a/vendor/topthink/framework/src/think/middleware/LoadLangPack.php b/vendor/topthink/framework/src/think/middleware/LoadLangPack.php new file mode 100644 index 0000000..c9e7a9d --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/LoadLangPack.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\App; +use think\Lang; +use think\Request; +use think\Response; + +/** + * 多语言加载 + */ +class LoadLangPack +{ + protected $app; + + protected $lang; + + public function __construct(App $app, Lang $lang) + { + $this->app = $app; + $this->lang = $lang; + } + + /** + * 路由初始化(路由规则注册) + * @access public + * @param Request $request + * @param Closure $next + * @return Response + */ + public function handle($request, Closure $next) + { + // 自动侦测当前语言 + $langset = $this->lang->detect($request); + + if ($this->lang->defaultLangSet() != $langset) { + // 加载系统语言包 + $this->lang->load([ + $this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php', + ]); + + $this->app->LoadLangPack($langset); + } + + $this->lang->saveToCookie($this->app->cookie); + + return $next($request); + } +} diff --git a/vendor/topthink/framework/src/think/middleware/SessionInit.php b/vendor/topthink/framework/src/think/middleware/SessionInit.php new file mode 100644 index 0000000..8c2a7b4 --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/SessionInit.php @@ -0,0 +1,80 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\App; +use think\Request; +use think\Response; +use think\Session; + +/** + * Session初始化 + */ +class SessionInit +{ + + /** @var App */ + protected $app; + + /** @var Session */ + protected $session; + + public function __construct(App $app, Session $session) + { + $this->app = $app; + $this->session = $session; + } + + /** + * Session初始化 + * @access public + * @param Request $request + * @param Closure $next + * @return Response + */ + public function handle($request, Closure $next) + { + // Session初始化 + $varSessionId = $this->app->config->get('session.var_session_id'); + $cookieName = $this->session->getName(); + + if ($varSessionId && $request->request($varSessionId)) { + $sessionId = $request->request($varSessionId); + } else { + $sessionId = $request->cookie($cookieName); + } + + if ($sessionId) { + $this->session->setId($sessionId); + } + + $this->session->init(); + + $request->withSession($this->session); + + /** @var Response $response */ + $response = $next($request); + + $response->setSession($this->session); + + $this->app->cookie->set($cookieName, $this->session->getId()); + + return $response; + } + + public function end(Response $response) + { + $this->session->save(); + } +} diff --git a/vendor/topthink/framework/src/think/response/File.php b/vendor/topthink/framework/src/think/response/File.php new file mode 100644 index 0000000..ce000ab --- /dev/null +++ b/vendor/topthink/framework/src/think/response/File.php @@ -0,0 +1,145 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Exception; +use think\Response; + +/** + * File Response + */ +class File extends Response +{ + protected $expire = 360; + protected $name; + protected $mimeType; + protected $isContent = false; + + public function __construct($data = '', int $code = 200) + { + $this->init($data, $code); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + if (!$this->isContent && !is_file($data)) { + throw new Exception('file not exists:' . $data); + } + + ob_end_clean(); + + if (!empty($this->name)) { + $name = $this->name; + } else { + $name = !$this->isContent ? pathinfo($data, PATHINFO_BASENAME) : ''; + } + + if ($this->isContent) { + $mimeType = $this->mimeType; + $size = strlen($data); + } else { + $mimeType = $this->getMimeType($data); + $size = filesize($data); + } + + $this->header['Pragma'] = 'public'; + $this->header['Content-Type'] = $mimeType ?: 'application/octet-stream'; + $this->header['Cache-control'] = 'max-age=' . $this->expire; + $this->header['Content-Disposition'] = 'attachment; filename="' . $name . '"'; + $this->header['Content-Length'] = $size; + $this->header['Content-Transfer-Encoding'] = 'binary'; + $this->header['Expires'] = gmdate("D, d M Y H:i:s", time() + $this->expire) . ' GMT'; + + $this->lastModified(gmdate('D, d M Y H:i:s', time()) . ' GMT'); + + return $this->isContent ? $data : file_get_contents($data); + } + + /** + * 设置是否为内容 必须配合mimeType方法使用 + * @access public + * @param bool $content + * @return $this + */ + public function isContent(bool $content = true) + { + $this->isContent = $content; + return $this; + } + + /** + * 设置有效期 + * @access public + * @param integer $expire 有效期 + * @return $this + */ + public function expire(int $expire) + { + $this->expire = $expire; + return $this; + } + + /** + * 设置文件类型 + * @access public + * @param string $filename 文件名 + * @return $this + */ + public function mimeType(string $mimeType) + { + $this->mimeType = $mimeType; + return $this; + } + + /** + * 获取文件类型信息 + * @access public + * @param string $filename 文件名 + * @return string + */ + protected function getMimeType(string $filename): string + { + if (!empty($this->mimeType)) { + return $this->mimeType; + } + + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $filename); + } + + /** + * 设置下载文件的显示名称 + * @access public + * @param string $filename 文件名 + * @param bool $extension 后缀自动识别 + * @return $this + */ + public function name(string $filename, bool $extension = true) + { + $this->name = $filename; + + if ($extension && false === strpos($filename, '.')) { + $this->name .= '.' . pathinfo($this->data, PATHINFO_EXTENSION); + } + + return $this; + } +} diff --git a/vendor/topthink/framework/src/think/response/Html.php b/vendor/topthink/framework/src/think/response/Html.php new file mode 100644 index 0000000..dbf23c7 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Html.php @@ -0,0 +1,34 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Response; + +/** + * Html Response + */ +class Html extends Response +{ + /** + * 输出type + * @var string + */ + protected $contentType = 'text/html'; + + public function __construct(Cookie $cookie, $data = '', int $code = 200) + { + $this->init($data, $code); + $this->cookie = $cookie; + } +} diff --git a/vendor/topthink/framework/src/think/response/Json.php b/vendor/topthink/framework/src/think/response/Json.php new file mode 100644 index 0000000..85d2d22 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Json.php @@ -0,0 +1,62 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Response; + +/** + * Json Response + */ +class Json extends Response +{ + // 输出参数 + protected $options = [ + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/json'; + + public function __construct(Cookie $cookie, $data = '', int $code = 200) + { + $this->init($data, $code); + $this->cookie = $cookie; + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return string + * @throws \Exception + */ + protected function output($data): string + { + try { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data, $this->options['json_encode_param']); + + if (false === $data) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/vendor/topthink/framework/src/think/response/Jsonp.php b/vendor/topthink/framework/src/think/response/Jsonp.php new file mode 100644 index 0000000..a098d4c --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Jsonp.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Request; +use think\Response; + +/** + * Jsonp Response + */ +class Jsonp extends Response +{ + // 输出参数 + protected $options = [ + 'var_jsonp_handler' => 'callback', + 'default_jsonp_handler' => 'jsonpReturn', + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/javascript'; + + protected $request; + + public function __construct(Cookie $cookie, Request $request, $data = '', int $code = 200) + { + $this->init($data, $code); + + $this->cookie = $cookie; + $this->request = $request; + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return string + * @throws \Exception + */ + protected function output($data): string + { + try { + // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] + $var_jsonp_handler = $this->request->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 (false === $data) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + $data = $handler . '(' . $data . ');'; + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/vendor/topthink/framework/src/think/response/Redirect.php b/vendor/topthink/framework/src/think/response/Redirect.php new file mode 100644 index 0000000..3d201aa --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Redirect.php @@ -0,0 +1,98 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Request; +use think\Response; +use think\Session; + +/** + * Redirect Response + */ +class Redirect extends Response +{ + + protected $request; + + public function __construct(Cookie $cookie, Request $request, Session $session, $data = '', int $code = 302) + { + $this->init((string) $data, $code); + + $this->cookie = $cookie; + $this->request = $request; + $this->session = $session; + + $this->cacheControl('no-cache,must-revalidate'); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return string + */ + protected function output($data): string + { + $this->header['Location'] = $data; + + return ''; + } + + /** + * 重定向传值(通过Session) + * @access protected + * @param string|array $name 变量名或者数组 + * @param mixed $value 值 + * @return $this + */ + public function with($name, $value = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + $this->session->flash($key, $val); + } + } else { + $this->session->flash($name, $value); + } + + return $this; + } + + /** + * 记住当前url后跳转 + * @access public + * @return $this + */ + public function remember() + { + $this->session->set('redirect_url', $this->request->url()); + + return $this; + } + + /** + * 跳转到上次记住的url + * @access public + * @return $this + */ + public function restore() + { + if ($this->session->has('redirect_url')) { + $this->data = $this->session->get('redirect_url'); + $this->session->delete('redirect_url'); + } + + return $this; + } +} diff --git a/vendor/topthink/framework/src/think/response/View.php b/vendor/topthink/framework/src/think/response/View.php new file mode 100644 index 0000000..0b1ae88 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/View.php @@ -0,0 +1,149 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Response; +use think\View as BaseView; + +/** + * View Response + */ +class View extends Response +{ + /** + * 输出参数 + * @var array + */ + protected $options = []; + + /** + * 输出变量 + * @var array + */ + protected $vars = []; + + /** + * 输出过滤 + * @var mixed + */ + protected $filter; + + /** + * 输出type + * @var string + */ + protected $contentType = 'text/html'; + + /** + * View对象 + * @var BaseView + */ + protected $view; + + /** + * 是否内容渲染 + * @var bool + */ + protected $isContent = false; + + public function __construct(Cookie $cookie, BaseView $view, $data = '', int $code = 200) + { + $this->init($data, $code); + + $this->cookie = $cookie; + $this->view = $view; + } + + /** + * 设置是否为内容渲染 + * @access public + * @param bool $content + * @return $this + */ + public function isContent(bool $content = true) + { + $this->isContent = $content; + return $this; + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return string + */ + protected function output($data): string + { + // 渲染模板输出 + return $this->view->filter($this->filter) + ->fetch($data, $this->vars, $this->isContent); + } + + /** + * 获取视图变量 + * @access public + * @param string $name 模板变量 + * @return mixed + */ + public function getVars(string $name = null) + { + if (is_null($name)) { + return $this->vars; + } else { + return $this->vars[$name] ?? null; + } + } + + /** + * 模板变量赋值 + * @access public + * @param string|array $name 模板变量 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = null) + { + if (is_array($name)) { + $this->vars = array_merge($this->vars, $name); + } else { + $this->vars[$name] = $value; + } + + return $this; + } + + /** + * 视图内容过滤 + * @access public + * @param callable $filter + * @return $this + */ + public function filter(callable $filter = null) + { + $this->filter = $filter; + return $this; + } + + /** + * 检查模板是否存在 + * @access public + * @param string $name 模板名 + * @return bool + */ + public function exists(string $name): bool + { + return $this->view->exists($name); + } + +} diff --git a/vendor/topthink/framework/src/think/response/Xml.php b/vendor/topthink/framework/src/think/response/Xml.php new file mode 100644 index 0000000..3597548 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Xml.php @@ -0,0 +1,127 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Collection; +use think\Cookie; +use think\Model; +use think\Response; + +/** + * XML Response + */ +class Xml extends Response +{ + // 输出参数 + protected $options = [ + // 根节点名 + 'root_node' => 'think', + // 根节点属性 + 'root_attr' => '', + //数字索引的子节点名 + 'item_node' => 'item', + // 数字索引子节点key转换的属性名 + 'item_key' => 'id', + // 数据编码 + 'encoding' => 'utf-8', + ]; + + protected $contentType = 'text/xml'; + + public function __construct(Cookie $cookie, $data = '', int $code = 200) + { + $this->init($data, $code); + $this->cookie = $cookie; + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data): string + { + if (is_string($data)) { + if (0 !== strpos($data, 'options['encoding']; + $xml = ""; + $data = $xml . $data; + } + return $data; + } + + // XML数据转换 + return $this->xmlEncode($data, $this->options['root_node'], $this->options['item_node'], $this->options['root_attr'], $this->options['item_key'], $this->options['encoding']); + } + + /** + * XML编码 + * @access protected + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param mixed $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 + * @return string + */ + protected function xmlEncode($data, string $root, string $item, $attr, string $id, string $encoding): string + { + if (is_array($attr)) { + $array = []; + foreach ($attr as $key => $value) { + $array[] = "{$key}=\"{$value}\""; + } + $attr = implode(' ', $array); + } + + $attr = trim($attr); + $attr = empty($attr) ? '' : " {$attr}"; + $xml = ""; + $xml .= "<{$root}{$attr}>"; + $xml .= $this->dataToXml($data, $item, $id); + $xml .= ""; + + return $xml; + } + + /** + * 数据XML编码 + * @access protected + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 + * @return string + */ + protected function dataToXml($data, string $item, string $id): string + { + $xml = $attr = ''; + + if ($data instanceof Collection || $data instanceof Model) { + $data = $data->toArray(); + } + + foreach ($data as $key => $val) { + if (is_numeric($key)) { + $id && $attr = " {$id}=\"{$key}\""; + $key = $item; + } + $xml .= "<{$key}{$attr}>"; + $xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val; + $xml .= ""; + } + + return $xml; + } +} diff --git a/vendor/topthink/framework/src/think/route/Dispatch.php b/vendor/topthink/framework/src/think/route/Dispatch.php new file mode 100644 index 0000000..dc39fd1 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Dispatch.php @@ -0,0 +1,265 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\App; +use think\Container; +use think\Request; +use think\Response; +use think\Validate; + +/** + * 路由调度基础类 + */ +abstract class Dispatch +{ + /** + * 应用对象 + * @var \think\App + */ + protected $app; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * 路由规则 + * @var Rule + */ + protected $rule; + + /** + * 调度信息 + * @var mixed + */ + protected $dispatch; + + /** + * 路由变量 + * @var array + */ + protected $param; + + /** + * 状态码 + * @var int + */ + protected $code; + + public function __construct(Request $request, Rule $rule, $dispatch, array $param = [], int $code = null) + { + $this->request = $request; + $this->rule = $rule; + $this->dispatch = $dispatch; + $this->param = $param; + $this->code = $code; + } + + public function init(App $app) + { + $this->app = $app; + + // 执行路由后置操作 + $this->doRouteAfter(); + } + + /** + * 执行路由调度 + * @access public + * @return mixed + */ + public function run(): Response + { + if ($this->rule instanceof RuleItem && $this->request->method() == 'OPTIONS' && $this->rule->isAutoOptions()) { + $rules = $this->rule->getRouter()->getRule($this->rule->getRule()); + $allow = []; + foreach ($rules as $item) { + $allow[] = strtoupper($item->getMethod()); + } + + return Response::create('', 'html', 204)->header(['Allow' => implode(', ', $allow)]); + } + + $data = $this->exec(); + return $this->autoResponse($data); + } + + protected function autoResponse($data): Response + { + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $type = $this->request->isJson() ? 'json' : 'html'; + $response = Response::create($data, $type); + } else { + $data = ob_get_clean(); + + $content = false === $data ? '' : $data; + $status = '' === $content && $this->request->isJson() ? 204 : 200; + $response = Response::create($content, 'html', $status); + } + + return $response; + } + + /** + * 检查路由后置操作 + * @access protected + * @return void + */ + protected function doRouteAfter(): void + { + $option = $this->rule->getOption(); + + // 添加中间件 + if (!empty($option['middleware'])) { + $this->app->middleware->import($option['middleware'], 'route'); + } + + if (!empty($option['append'])) { + $this->param = array_merge($this->param, $option['append']); + } + + // 绑定模型数据 + if (!empty($option['model'])) { + $this->createBindModel($option['model'], $this->param); + } + + // 记录当前请求的路由规则 + $this->request->setRule($this->rule); + + // 记录路由变量 + $this->request->setRoute($this->param); + + // 数据自动验证 + if (isset($option['validate'])) { + $this->autoValidate($option['validate']); + } + } + + /** + * 路由绑定模型实例 + * @access protected + * @param array $bindModel 绑定模型 + * @param array $matches 路由变量 + * @return void + */ + protected function createBindModel(array $bindModel, array $matches): void + { + foreach ($bindModel as $key => $val) { + if ($val instanceof \Closure) { + $result = $this->app->invokeFunction($val, $matches); + } else { + $fields = explode('&', $key); + + if (is_array($val)) { + [$model, $exception] = $val; + } else { + $model = $val; + $exception = true; + } + + $where = []; + $match = true; + + foreach ($fields as $field) { + if (!isset($matches[$field])) { + $match = false; + break; + } else { + $where[] = [$field, '=', $matches[$field]]; + } + } + + if ($match) { + $result = $model::where($where)->failException($exception)->find(); + } + } + + if (!empty($result)) { + // 注入容器 + $this->app->instance(get_class($result), $result); + } + } + } + + /** + * 验证数据 + * @access protected + * @param array $option + * @return void + * @throws \think\exception\ValidateException + */ + protected function autoValidate(array $option): void + { + [$validate, $scene, $message, $batch] = $option; + + if (is_array($validate)) { + // 指定验证规则 + $v = new Validate(); + $v->rule($validate); + } else { + // 调用验证器 + $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate); + + $v = new $class(); + + if (!empty($scene)) { + $v->scene($scene); + } + } + + /** @var Validate $v */ + $v->message($message) + ->batch($batch) + ->failException(true) + ->check($this->request->param()); + } + + public function getDispatch() + { + return $this->dispatch; + } + + public function getParam(): array + { + return $this->param; + } + + abstract public function exec(); + + public function __sleep() + { + return ['rule', 'dispatch', 'param', 'code', 'controller', 'actionName']; + } + + public function __wakeup() + { + $this->app = Container::pull('app'); + $this->request = $this->app->request; + } + + public function __debugInfo() + { + return [ + 'dispatch' => $this->dispatch, + 'param' => $this->param, + 'code' => $this->code, + 'rule' => $this->rule, + ]; + } +} diff --git a/vendor/topthink/framework/src/think/route/Domain.php b/vendor/topthink/framework/src/think/route/Domain.php new file mode 100644 index 0000000..8c04af0 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Domain.php @@ -0,0 +1,183 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\helper\Str; +use think\Request; +use think\Route; +use think\route\dispatch\Callback as CallbackDispatch; +use think\route\dispatch\Controller as ControllerDispatch; + +/** + * 域名路由 + */ +class Domain extends RuleGroup +{ + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param string $name 路由域名 + * @param mixed $rule 域名路由 + */ + public function __construct(Route $router, string $name = null, $rule = null) + { + $this->router = $router; + $this->domain = $name; + $this->rule = $rule; + } + + /** + * 检测域名路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check(Request $request, string $url, bool $completeMatch = false) + { + // 检测URL绑定 + $result = $this->checkUrlBind($request, $url); + + if (!empty($this->option['append'])) { + $request->setRoute($this->option['append']); + unset($this->option['append']); + } + + if (false !== $result) { + return $result; + } + + return parent::check($request, $url, $completeMatch); + } + + /** + * 设置路由绑定 + * @access public + * @param string $bind 绑定信息 + * @return $this + */ + public function bind(string $bind) + { + $this->router->bind($bind, $this->domain); + + return $this; + } + + /** + * 检测URL绑定 + * @access private + * @param Request $request + * @param string $url URL地址 + * @return Dispatch|false + */ + private function checkUrlBind(Request $request, string $url) + { + $bind = $this->router->getDomainBind($this->domain); + + if ($bind) { + $this->parseBindAppendParam($bind); + + // 如果有URL绑定 则进行绑定检测 + $type = substr($bind, 0, 1); + $bind = substr($bind, 1); + + $bindTo = [ + '\\' => 'bindToClass', + '@' => 'bindToController', + ':' => 'bindToNamespace', + ]; + + if (isset($bindTo[$type])) { + return $this->{$bindTo[$type]}($request, $url, $bind); + } + } + + return false; + } + + protected function parseBindAppendParam(string &$bind): void + { + if (false !== strpos($bind, '?')) { + [$bind, $query] = explode('?', $bind); + parse_str($query, $vars); + $this->append($vars); + } + } + + /** + * 绑定到类 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $class 类名(带命名空间) + * @return CallbackDispatch + */ + protected function bindToClass(Request $request, string $url, string $class): CallbackDispatch + { + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[1])) { + $this->parseUrlParams($array[1], $param); + } + + return new CallbackDispatch($request, $this, [$class, $action], $param); + } + + /** + * 绑定到命名空间 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $namespace 命名空间 + * @return CallbackDispatch + */ + protected function bindToNamespace(Request $request, string $url, string $namespace): CallbackDispatch + { + $array = explode('|', $url, 3); + $class = !empty($array[0]) ? $array[0] : $this->router->config('default_controller'); + $method = !empty($array[1]) ? $array[1] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[2])) { + $this->parseUrlParams($array[2], $param); + } + + return new CallbackDispatch($request, $this, [$namespace . '\\' . Str::studly($class), $method], $param); + } + + /** + * 绑定到控制器 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $controller 控制器名 + * @return ControllerDispatch + */ + protected function bindToController(Request $request, string $url, string $controller): ControllerDispatch + { + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[1])) { + $this->parseUrlParams($array[1], $param); + } + + return new ControllerDispatch($request, $this, $controller . '/' . $action, $param); + } + +} diff --git a/vendor/topthink/framework/src/think/route/Resource.php b/vendor/topthink/framework/src/think/route/Resource.php new file mode 100644 index 0000000..01565fc --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Resource.php @@ -0,0 +1,251 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\Route; + +/** + * 资源路由类 + */ +class Resource extends RuleGroup +{ + /** + * 资源路由名称 + * @var string + */ + protected $resource; + + /** + * 资源路由地址 + * @var string + */ + protected $route; + + /** + * REST方法定义 + * @var array + */ + protected $rest = []; + + /** + * 模型绑定 + * @var array + */ + protected $model = []; + + /** + * 数据验证 + * @var array + */ + protected $validate = []; + + /** + * 中间件 + * @var array + */ + protected $middleware = []; + + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param RuleGroup $parent 上级对象 + * @param string $name 资源名称 + * @param string $route 路由地址 + * @param array $rest 资源定义 + */ + public function __construct(Route $router, RuleGroup $parent = null, string $name = '', string $route = '', array $rest = []) + { + $name = ltrim($name, '/'); + $this->router = $router; + $this->parent = $parent; + $this->resource = $name; + $this->route = $route; + $this->name = strpos($name, '.') ? strstr($name, '.', true) : $name; + + $this->setFullName(); + + // 资源路由默认为完整匹配 + $this->option['complete_match'] = true; + + $this->rest = $rest; + + if ($this->parent) { + $this->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } + + if ($router->isTest()) { + $this->buildResourceRule(); + } + } + + /** + * 生成资源路由规则 + * @access protected + * @return void + */ + protected function buildResourceRule(): void + { + $rule = $this->resource; + $option = $this->option; + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + + if (strpos($rule, '.')) { + // 注册嵌套资源路由 + $array = explode('.', $rule); + $last = array_pop($array); + $item = []; + + foreach ($array as $val) { + $item[] = $val . '/<' . ($option['var'][$val] ?? $val . '_id') . '>'; + } + + $rule = implode('/', $item) . '/' . $last; + } + + $prefix = substr($rule, strlen($this->name) + 1); + + // 注册资源路由 + foreach ($this->rest as $key => $val) { + if ((isset($option['only']) && !in_array($key, $option['only'])) + || (isset($option['except']) && in_array($key, $option['except']))) { + continue; + } + + if (isset($last) && strpos($val[1], '') && isset($option['var'][$last])) { + $val[1] = str_replace('', '<' . $option['var'][$last] . '>', $val[1]); + } elseif (strpos($val[1], '') && isset($option['var'][$rule])) { + $val[1] = str_replace('', '<' . $option['var'][$rule] . '>', $val[1]); + } + + $ruleItem = $this->addRule(trim($prefix . $val[1], '/'), $this->route . '/' . $val[2], $val[0]); + + foreach (['model', 'validate', 'middleware'] as $name) { + if (isset($this->$name[$key])) { + call_user_func_array([$ruleItem, $name], (array) $this->$name[$key]); + } + + } + } + + $this->router->setGroup($origin); + } + + /** + * 设置资源允许 + * @access public + * @param array $only 资源允许 + * @return $this + */ + public function only(array $only) + { + return $this->setOption('only', $only); + } + + /** + * 设置资源排除 + * @access public + * @param array $except 排除资源 + * @return $this + */ + public function except(array $except) + { + return $this->setOption('except', $except); + } + + /** + * 设置资源路由的变量 + * @access public + * @param array $vars 资源变量 + * @return $this + */ + public function vars(array $vars) + { + return $this->setOption('var', $vars); + } + + /** + * 绑定资源验证 + * @access public + * @param array|string $name 资源类型或者验证信息 + * @param array|string $validate 验证信息 + * @return $this + */ + public function withValidate($name, $validate = []) + { + if (is_array($name)) { + $this->validate = array_merge($this->validate, $name); + } else { + $this->validate[$name] = $validate; + } + + return $this; + } + + /** + * 绑定资源模型 + * @access public + * @param array|string $name 资源类型或者模型绑定 + * @param array|string $model 模型绑定 + * @return $this + */ + public function withModel($name, $model = []) + { + if (is_array($name)) { + $this->model = array_merge($this->model, $name); + } else { + $this->model[$name] = $model; + } + + return $this; + } + + /** + * 绑定资源模型 + * @access public + * @param array|string $name 资源类型或者中间件定义 + * @param array|string $middleware 中间件定义 + * @return $this + */ + public function withMiddleware($name, $middleware = []) + { + if (is_array($name)) { + $this->middleware = array_merge($this->middleware, $name); + } else { + $this->middleware[$name] = $middleware; + } + + return $this; + } + + /** + * rest方法定义和修改 + * @access public + * @param array|string $name 方法名称 + * @param array|bool $resource 资源 + * @return $this + */ + public function rest($name, $resource = []) + { + if (is_array($name)) { + $this->rest = $resource ? $name : array_merge($this->rest, $name); + } else { + $this->rest[$name] = $resource; + } + + return $this; + } + +} diff --git a/vendor/topthink/framework/src/think/route/Rule.php b/vendor/topthink/framework/src/think/route/Rule.php new file mode 100644 index 0000000..fd9e280 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Rule.php @@ -0,0 +1,925 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use Closure; +use think\Container; +use think\middleware\AllowCrossDomain; +use think\middleware\CheckRequestCache; +use think\middleware\FormTokenCheck; +use think\Request; +use think\Response; +use think\Route; +use think\route\dispatch\Callback as CallbackDispatch; +use think\route\dispatch\Controller as ControllerDispatch; +use think\route\dispatch\Redirect as RedirectDispatch; +use think\route\dispatch\Response as ResponseDispatch; +use think\route\dispatch\View as ViewDispatch; + +/** + * 路由规则基础类 + */ +abstract class Rule +{ + /** + * 路由标识 + * @var string + */ + protected $name; + + /** + * 所在域名 + * @var string + */ + protected $domain; + + /** + * 路由对象 + * @var Route + */ + protected $router; + + /** + * 路由所属分组 + * @var RuleGroup + */ + protected $parent; + + /** + * 路由规则 + * @var mixed + */ + protected $rule; + + /** + * 路由地址 + * @var string|Closure + */ + protected $route; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 路由变量 + * @var array + */ + protected $vars = []; + + /** + * 路由参数 + * @var array + */ + protected $option = []; + + /** + * 路由变量规则 + * @var array + */ + protected $pattern = []; + + /** + * 需要和分组合并的路由参数 + * @var array + */ + protected $mergeOptions = ['model', 'append', 'middleware']; + + abstract public function check(Request $request, string $url, bool $completeMatch = false); + + /** + * 设置路由参数 + * @access public + * @param array $option 参数 + * @return $this + */ + public function option(array $option) + { + $this->option = array_merge($this->option, $option); + + return $this; + } + + /** + * 设置单个路由参数 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function setOption(string $name, $value) + { + $this->option[$name] = $value; + + return $this; + } + + /** + * 注册变量规则 + * @access public + * @param array $pattern 变量规则 + * @return $this + */ + public function pattern(array $pattern) + { + $this->pattern = array_merge($this->pattern, $pattern); + + return $this; + } + + /** + * 设置标识 + * @access public + * @param string $name 标识名 + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + + return $this; + } + + /** + * 获取路由对象 + * @access public + * @return Route + */ + public function getRouter(): Route + { + return $this->router; + } + + /** + * 获取Name + * @access public + * @return string + */ + public function getName(): string + { + return $this->name ?: ''; + } + + /** + * 获取当前路由规则 + * @access public + * @return mixed + */ + public function getRule() + { + return $this->rule; + } + + /** + * 获取当前路由地址 + * @access public + * @return mixed + */ + public function getRoute() + { + return $this->route; + } + + /** + * 获取当前路由的变量 + * @access public + * @return array + */ + public function getVars(): array + { + return $this->vars; + } + + /** + * 获取Parent对象 + * @access public + * @return $this|null + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取路由所在域名 + * @access public + * @return string + */ + public function getDomain(): string + { + return $this->domain ?: $this->parent->getDomain(); + } + + /** + * 获取路由参数 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function config(string $name = '') + { + return $this->router->config($name); + } + + /** + * 获取变量规则定义 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function getPattern(string $name = '') + { + if ('' === $name) { + return $this->pattern; + } + + return $this->pattern[$name] ?? null; + } + + /** + * 获取路由参数定义 + * @access public + * @param string $name 参数名 + * @param mixed $default 默认值 + * @return mixed + */ + public function getOption(string $name = '', $default = null) + { + if ('' === $name) { + return $this->option; + } + + return $this->option[$name] ?? $default; + } + + /** + * 获取当前路由的请求类型 + * @access public + * @return string + */ + public function getMethod(): string + { + return strtolower($this->method); + } + + /** + * 设置路由请求类型 + * @access public + * @param string $method 请求类型 + * @return $this + */ + public function method(string $method) + { + return $this->setOption('method', strtolower($method)); + } + + /** + * 检查后缀 + * @access public + * @param string $ext URL后缀 + * @return $this + */ + public function ext(string $ext = '') + { + return $this->setOption('ext', $ext); + } + + /** + * 检查禁止后缀 + * @access public + * @param string $ext URL后缀 + * @return $this + */ + public function denyExt(string $ext = '') + { + return $this->setOption('deny_ext', $ext); + } + + /** + * 检查域名 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function domain(string $domain) + { + $this->domain = $domain; + return $this->setOption('domain', $domain); + } + + /** + * 设置参数过滤检查 + * @access public + * @param array $filter 参数过滤 + * @return $this + */ + public function filter(array $filter) + { + $this->option['filter'] = $filter; + + return $this; + } + + /** + * 绑定模型 + * @access public + * @param array|string|Closure $var 路由变量名 多个使用 & 分割 + * @param string|Closure $model 绑定模型类 + * @param bool $exception 是否抛出异常 + * @return $this + */ + public function model($var, $model = null, bool $exception = true) + { + if ($var instanceof Closure) { + $this->option['model'][] = $var; + } elseif (is_array($var)) { + $this->option['model'] = $var; + } elseif (is_null($model)) { + $this->option['model']['id'] = [$var, true]; + } else { + $this->option['model'][$var] = [$model, $exception]; + } + + return $this; + } + + /** + * 附加路由隐式参数 + * @access public + * @param array $append 追加参数 + * @return $this + */ + public function append(array $append = []) + { + $this->option['append'] = $append; + + return $this; + } + + /** + * 绑定验证 + * @access public + * @param mixed $validate 验证器类 + * @param string $scene 验证场景 + * @param array $message 验证提示 + * @param bool $batch 批量验证 + * @return $this + */ + public function validate($validate, string $scene = null, array $message = [], bool $batch = false) + { + $this->option['validate'] = [$validate, $scene, $message, $batch]; + + return $this; + } + + /** + * 指定路由中间件 + * @access public + * @param string|array|Closure $middleware 中间件 + * @param mixed $param 参数 + * @return $this + */ + public function middleware($middleware, $param = null) + { + if (is_null($param) && is_array($middleware)) { + $this->option['middleware'] = $middleware; + } else { + foreach ((array) $middleware as $item) { + $this->option['middleware'][] = [$item, $param]; + } + } + + return $this; + } + + /** + * 允许跨域 + * @access public + * @param array $header 自定义Header + * @return $this + */ + public function allowCrossDomain(array $header = []) + { + return $this->middleware(AllowCrossDomain::class, $header); + } + + /** + * 表单令牌验证 + * @access public + * @param string $token 表单令牌token名称 + * @return $this + */ + public function token(string $token = '__token__') + { + return $this->middleware(FormTokenCheck::class, $token); + } + + /** + * 设置路由缓存 + * @access public + * @param array|string $cache 缓存 + * @return $this + */ + public function cache($cache) + { + return $this->middleware(CheckRequestCache::class, $cache); + } + + /** + * 检查URL分隔符 + * @access public + * @param string $depr URL分隔符 + * @return $this + */ + public function depr(string $depr) + { + return $this->setOption('param_depr', $depr); + } + + /** + * 设置需要合并的路由参数 + * @access public + * @param array $option 路由参数 + * @return $this + */ + public function mergeOptions(array $option = []) + { + $this->mergeOptions = array_merge($this->mergeOptions, $option); + return $this; + } + + /** + * 检查是否为HTTPS请求 + * @access public + * @param bool $https 是否为HTTPS + * @return $this + */ + public function https(bool $https = true) + { + return $this->setOption('https', $https); + } + + /** + * 检查是否为JSON请求 + * @access public + * @param bool $json 是否为JSON + * @return $this + */ + public function json(bool $json = true) + { + return $this->setOption('json', $json); + } + + /** + * 检查是否为AJAX请求 + * @access public + * @param bool $ajax 是否为AJAX + * @return $this + */ + public function ajax(bool $ajax = true) + { + return $this->setOption('ajax', $ajax); + } + + /** + * 检查是否为PJAX请求 + * @access public + * @param bool $pjax 是否为PJAX + * @return $this + */ + public function pjax(bool $pjax = true) + { + return $this->setOption('pjax', $pjax); + } + + /** + * 当前路由到一个模板地址 当使用数组的时候可以传入模板变量 + * @access public + * @param bool|array $view 视图 + * @return $this + */ + public function view($view = true) + { + return $this->setOption('view', $view); + } + + /** + * 当前路由为重定向 + * @access public + * @param bool $redirect 是否为重定向 + * @return $this + */ + public function redirect(bool $redirect = true) + { + return $this->setOption('redirect', $redirect); + } + + /** + * 设置status + * @access public + * @param int $status 状态码 + * @return $this + */ + public function status(int $status) + { + return $this->setOption('status', $status); + } + + /** + * 设置路由完整匹配 + * @access public + * @param bool $match 是否完整匹配 + * @return $this + */ + public function completeMatch(bool $match = true) + { + return $this->setOption('complete_match', $match); + } + + /** + * 是否去除URL最后的斜线 + * @access public + * @param bool $remove 是否去除最后斜线 + * @return $this + */ + public function removeSlash(bool $remove = true) + { + return $this->setOption('remove_slash', $remove); + } + + /** + * 设置路由规则全局有效 + * @access public + * @return $this + */ + public function crossDomainRule() + { + if ($this instanceof RuleGroup) { + $method = '*'; + } else { + $method = $this->method; + } + + $this->router->setCrossDomainRule($this, $method); + + return $this; + } + + /** + * 合并分组参数 + * @access public + * @return array + */ + public function mergeGroupOptions(): array + { + $parentOption = $this->parent->getOption(); + // 合并分组参数 + foreach ($this->mergeOptions as $item) { + if (isset($parentOption[$item]) && isset($this->option[$item])) { + $this->option[$item] = array_merge($parentOption[$item], $this->option[$item]); + } + } + + $this->option = array_merge($parentOption, $this->option); + + return $this->option; + } + + /** + * 解析匹配到的规则路由 + * @access public + * @param Request $request 请求对象 + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $url URL地址 + * @param array $option 路由参数 + * @param array $matches 匹配的变量 + * @return Dispatch + */ + public function parseRule(Request $request, string $rule, $route, string $url, array $option = [], array $matches = []): Dispatch + { + if (is_string($route) && isset($option['prefix'])) { + // 路由地址前缀 + $route = $option['prefix'] . $route; + } + + // 替换路由地址中的变量 + if (is_string($route) && !empty($matches)) { + $search = $replace = []; + + foreach ($matches as $key => $value) { + $search[] = '<' . $key . '>'; + $replace[] = $value; + + $search[] = ':' . $key; + $replace[] = $value; + } + + $route = str_replace($search, $replace, $route); + } + + // 解析额外参数 + $count = substr_count($rule, '/'); + $url = array_slice(explode('|', $url), $count + 1); + $this->parseUrlParams(implode('|', $url), $matches); + + $this->vars = $matches; + + // 发起路由调度 + return $this->dispatch($request, $route, $option); + } + + /** + * 发起路由调度 + * @access protected + * @param Request $request Request对象 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @return Dispatch + */ + protected function dispatch(Request $request, $route, array $option): Dispatch + { + if ($route instanceof Dispatch) { + $result = $route; + } elseif ($route instanceof Closure) { + // 执行闭包 + $result = new CallbackDispatch($request, $this, $route, $this->vars); + } elseif ($route instanceof Response) { + $result = new ResponseDispatch($request, $this, $route); + } elseif (isset($option['view']) && false !== $option['view']) { + $result = new ViewDispatch($request, $this, $route, is_array($option['view']) ? $option['view'] : $this->vars); + } elseif (!empty($option['redirect'])) { + // 路由到重定向地址 + $result = new RedirectDispatch($request, $this, $route, $this->vars, $option['status'] ?? 301); + } elseif (false !== strpos($route, '\\')) { + // 路由到类的方法 + $result = $this->dispatchMethod($request, $route); + } else { + // 路由到控制器/操作 + $result = $this->dispatchController($request, $route); + } + + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param Request $request Request对象 + * @param string $route 路由地址 + * @return CallbackDispatch + */ + protected function dispatchMethod(Request $request, string $route): CallbackDispatch + { + $path = $this->parseUrlPath($route); + + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + + return new CallbackDispatch($request, $this, $method, $this->vars); + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param Request $request Request对象 + * @param string $route 路由地址 + * @return ControllerDispatch + */ + protected function dispatchController(Request $request, string $route): ControllerDispatch + { + $path = $this->parseUrlPath($route); + + $action = array_pop($path); + $controller = !empty($path) ? array_pop($path) : null; + + // 路由到模块/控制器/操作 + return new ControllerDispatch($request, $this, [$controller, $action], $this->vars); + } + + /** + * 路由检查 + * @access protected + * @param array $option 路由参数 + * @param Request $request Request对象 + * @return bool + */ + protected function checkOption(array $option, Request $request): bool + { + // 请求类型检测 + if (!empty($option['method'])) { + if (is_string($option['method']) && false === stripos($option['method'], $request->method())) { + return false; + } + } + + // AJAX PJAX 请求检查 + foreach (['ajax', 'pjax', 'json'] as $item) { + if (isset($option[$item])) { + $call = 'is' . $item; + if ($option[$item] && !$request->$call() || !$option[$item] && $request->$call()) { + return false; + } + } + } + + // 伪静态后缀检测 + if ($request->url() != '/' && ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')))) { + return false; + } + + // 域名检查 + if ((isset($option['domain']) && !in_array($option['domain'], [$request->host(true), $request->subDomain()]))) { + return false; + } + + // HTTPS检查 + if ((isset($option['https']) && $option['https'] && !$request->isSsl()) + || (isset($option['https']) && !$option['https'] && $request->isSsl())) { + return false; + } + + // 请求参数检查 + if (isset($option['filter'])) { + foreach ($option['filter'] as $name => $value) { + if ($request->param($name, '', null) != $value) { + return false; + } + } + } + + return true; + } + + /** + * 解析URL地址中的参数Request对象 + * @access protected + * @param string $rule 路由规则 + * @param array $var 变量 + * @return void + */ + protected function parseUrlParams(string $url, array &$var = []): void + { + if ($url) { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, $url); + } + } + + /** + * 解析URL的pathinfo参数 + * @access public + * @param string $url URL地址 + * @return array + */ + public function parseUrlPath(string $url): array + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace('|', '/', $url); + $url = trim($url, '/'); + + if (strpos($url, '/')) { + // [控制器/操作] + $path = explode('/', $url); + } else { + $path = [$url]; + } + + return $path; + } + + /** + * 生成路由的正则规则 + * @access protected + * @param string $rule 路由规则 + * @param array $match 匹配的变量 + * @param array $pattern 路由变量规则 + * @param array $option 路由参数 + * @param bool $completeMatch 路由是否完全匹配 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildRuleRegex(string $rule, array $match, array $pattern = [], array $option = [], bool $completeMatch = false, string $suffix = ''): string + { + foreach ($match as $name) { + $replace[] = $this->buildNameRegex($name, $pattern, $suffix); + } + + // 是否区分 / 地址访问 + if ('/' != $rule) { + if (!empty($option['remove_slash'])) { + $rule = rtrim($rule, '/'); + } elseif (substr($rule, -1) == '/') { + $rule = rtrim($rule, '/'); + $hasSlash = true; + } + } + + $regex = str_replace(array_unique($match), array_unique($replace), $rule); + $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex); + + if (isset($hasSlash)) { + $regex .= '\/'; + } + + return $regex . ($completeMatch ? '$' : ''); + } + + /** + * 生成路由变量的正则规则 + * @access protected + * @param string $name 路由变量 + * @param array $pattern 变量规则 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildNameRegex(string $name, array $pattern, string $suffix): string + { + $optional = ''; + $slash = substr($name, 0, 1); + + if (in_array($slash, ['/', '-'])) { + $prefix = '\\' . $slash; + $name = substr($name, 1); + $slash = substr($name, 0, 1); + } else { + $prefix = ''; + } + + if ('<' != $slash) { + return $prefix . preg_quote($name, '/'); + } + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = '?'; + } elseif (strpos($name, '>')) { + $name = substr($name, 1, -1); + } + + if (isset($pattern[$name])) { + $nameRule = $pattern[$name]; + if (0 === strpos($nameRule, '/') && '/' == substr($nameRule, -1)) { + $nameRule = substr($nameRule, 1, -1); + } + } else { + $nameRule = $this->router->config('default_route_pattern'); + } + + return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional; + } + + /** + * 设置路由参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return $this + */ + public function __call($method, $args) + { + if (count($args) > 1) { + $args[0] = $args; + } + array_unshift($args, $method); + + return call_user_func_array([$this, 'setOption'], $args); + } + + public function __sleep() + { + return ['name', 'rule', 'route', 'method', 'vars', 'option', 'pattern']; + } + + public function __wakeup() + { + $this->router = Container::pull('route'); + } + + public function __debugInfo() + { + return [ + 'name' => $this->name, + 'rule' => $this->rule, + 'route' => $this->route, + 'method' => $this->method, + 'vars' => $this->vars, + 'option' => $this->option, + 'pattern' => $this->pattern, + ]; + } +} diff --git a/vendor/topthink/framework/src/think/route/RuleGroup.php b/vendor/topthink/framework/src/think/route/RuleGroup.php new file mode 100644 index 0000000..5bfff9b --- /dev/null +++ b/vendor/topthink/framework/src/think/route/RuleGroup.php @@ -0,0 +1,541 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use Closure; +use think\Container; +use think\Exception; +use think\Request; +use think\Response; +use think\Route; +use think\route\dispatch\Response as ResponseDispatch; + +/** + * 路由分组类 + */ +class RuleGroup extends Rule +{ + /** + * 分组路由(包括子分组) + * @var array + */ + protected $rules = [ + '*' => [], + 'get' => [], + 'post' => [], + 'put' => [], + 'patch' => [], + 'delete' => [], + 'head' => [], + 'options' => [], + ]; + + /** + * 分组路由规则 + * @var mixed + */ + protected $rule; + + /** + * MISS路由 + * @var RuleItem + */ + protected $miss; + + /** + * 完整名称 + * @var string + */ + protected $fullName; + + /** + * 分组别名 + * @var string + */ + protected $alias; + + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param RuleGroup $parent 上级对象 + * @param string $name 分组名称 + * @param mixed $rule 分组路由 + */ + public function __construct(Route $router, RuleGroup $parent = null, string $name = '', $rule = null) + { + $this->router = $router; + $this->parent = $parent; + $this->rule = $rule; + $this->name = trim($name, '/'); + + $this->setFullName(); + + if ($this->parent) { + $this->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } + + if ($router->isTest()) { + $this->lazy(false); + } + } + + /** + * 设置分组的路由规则 + * @access public + * @return void + */ + protected function setFullName(): void + { + if (false !== strpos($this->name, ':')) { + $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name); + } + + if ($this->parent && $this->parent->getFullName()) { + $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : ''); + } else { + $this->fullName = $this->name; + } + + if ($this->name) { + $this->router->getRuleName()->setGroup($this->name, $this); + } + } + + /** + * 获取所属域名 + * @access public + * @return string + */ + public function getDomain(): string + { + return $this->domain ?: '-'; + } + + /** + * 获取分组别名 + * @access public + * @return string + */ + public function getAlias(): string + { + return $this->alias ?: ''; + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check(Request $request, string $url, bool $completeMatch = false) + { + // 检查分组有效性 + if (!$this->checkOption($this->option, $request) || !$this->checkUrl($url)) { + return false; + } + + // 解析分组路由 + if ($this instanceof Resource) { + $this->buildResourceRule(); + } elseif ($this->rule instanceof Response) { + return new ResponseDispatch($request, $this, $this->rule); + } else { + $this->parseGroupRule($this->rule); + } + + // 获取当前路由规则 + $method = strtolower($request->method()); + $rules = $this->getMethodRules($method); + + if ($this->parent) { + // 合并分组参数 + $this->mergeGroupOptions(); + // 合并分组变量规则 + $this->pattern = array_merge($this->parent->getPattern(), $this->pattern); + } + + if (isset($this->option['complete_match'])) { + $completeMatch = $this->option['complete_match']; + } + + if (!empty($this->option['merge_rule_regex'])) { + // 合并路由正则规则进行路由匹配检查 + $result = $this->checkMergeRuleRegex($request, $rules, $url, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + // 检查分组路由 + foreach ($rules as $key => $item) { + $result = $item->check($request, $url, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + if ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) { + // 未匹配所有路由的路由规则处理 + $result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->mergeGroupOptions()); + } else { + $result = false; + } + + return $result; + } + + /** + * 获取当前请求的路由规则(包括子分组、资源路由) + * @access protected + * @param string $method 请求类型 + * @return array + */ + protected function getMethodRules(string $method): array + { + return array_merge($this->rules[$method], $this->rules['*']); + } + + /** + * 分组URL匹配检查 + * @access protected + * @param string $url URL + * @return bool + */ + protected function checkUrl(string $url): bool + { + if ($this->fullName) { + $pos = strpos($this->fullName, '<'); + + if (false !== $pos) { + $str = substr($this->fullName, 0, $pos); + } else { + $str = $this->fullName; + } + + if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) { + return false; + } + } + + return true; + } + + /** + * 设置路由分组别名 + * @access public + * @param string $alias 路由分组别名 + * @return $this + */ + public function alias(string $alias) + { + $this->alias = $alias; + $this->router->getRuleName()->setGroup($alias, $this); + + return $this; + } + + /** + * 延迟解析分组的路由规则 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy(bool $lazy = true) + { + if (!$lazy) { + $this->parseGroupRule($this->rule); + $this->rule = null; + } + + return $this; + } + + /** + * 解析分组和域名的路由规则及绑定 + * @access public + * @param mixed $rule 路由规则 + * @return void + */ + public function parseGroupRule($rule): void + { + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + + if ($rule instanceof \Closure) { + Container::getInstance()->invokeFunction($rule); + } elseif (is_string($rule) && $rule) { + $this->router->bind($rule, $this->domain); + } + + $this->router->setGroup($origin); + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param array $rules 路由规则 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + protected function checkMergeRuleRegex(Request $request, array &$rules, string $url, bool $completeMatch) + { + $depr = $this->router->config('pathinfo_depr'); + $url = $depr . str_replace('|', $depr, $url); + $regex = []; + $items = []; + + foreach ($rules as $key => $item) { + if ($item instanceof RuleItem) { + $rule = $depr . str_replace('/', $depr, $item->getRule()); + if ($depr == $rule && $depr != $url) { + unset($rules[$key]); + continue; + } + + $complete = $item->getOption('complete_match', $completeMatch); + + if (false === strpos($rule, '<')) { + if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) { + return $item->checkRule($request, $url, []); + } + + unset($rules[$key]); + continue; + } + + $slash = preg_quote('/-' . $depr, '/'); + + if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + unset($rules[$key]); + continue; + } + } + + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { + unset($rules[$key]); + $pattern = array_merge($this->getPattern(), $item->getPattern()); + $option = array_merge($this->getOption(), $item->getOption()); + + $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key); + $items[$key] = $item; + } + } + } + + if (empty($regex)) { + return false; + } + + try { + $result = preg_match('/^(?:' . implode('|', $regex) . ')/u', $url, $match); + } catch (\Exception $e) { + throw new Exception('route pattern error'); + } + + if ($result) { + $var = []; + foreach ($match as $key => $val) { + if (is_string($key) && '' !== $val) { + [$name, $pos] = explode('_THINK_', $key); + + $var[$name] = $val; + } + } + + if (!isset($pos)) { + foreach ($regex as $key => $item) { + if (0 === strpos(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) { + $pos = $key; + break; + } + } + } + + $rule = $items[$pos]->getRule(); + $array = $this->router->getRule($rule); + + foreach ($array as $item) { + if (in_array($item->getMethod(), ['*', strtolower($request->method())])) { + $result = $item->checkRule($request, $url, $var); + + if (false !== $result) { + return $result; + } + } + } + } + + return false; + } + + /** + * 获取分组的MISS路由 + * @access public + * @return RuleItem|null + */ + public function getMissRule(): ? RuleItem + { + return $this->miss; + } + + /** + * 注册MISS路由 + * @access public + * @param string|Closure $route 路由地址 + * @param string $method 请求类型 + * @return RuleItem + */ + public function miss($route, string $method = '*') : RuleItem + { + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method)); + + $ruleItem->setMiss(); + $this->miss = $ruleItem; + + return $ruleItem; + } + + /** + * 添加分组下的路由规则 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $method 请求类型 + * @return RuleItem + */ + public function addRule(string $rule, $route = null, string $method = '*'): RuleItem + { + // 读取路由标识 + if (is_string($route)) { + $name = $route; + } else { + $name = null; + } + + $method = strtolower($method); + + if ('' === $rule || '/' === $rule) { + $rule .= '$'; + } + + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method); + + $this->addRuleItem($ruleItem, $method); + + return $ruleItem; + } + + /** + * 注册分组下的路由规则 + * @access public + * @param Rule $rule 路由规则 + * @param string $method 请求类型 + * @return $this + */ + public function addRuleItem(Rule $rule, string $method = '*') + { + if (strpos($method, '|')) { + $rule->method($method); + $method = '*'; + } + + $this->rules[$method][] = $rule; + + if ($rule instanceof RuleItem && 'options' != $method) { + $this->rules['options'][] = $rule->setAutoOptions(); + } + + return $this; + } + + /** + * 设置分组的路由前缀 + * @access public + * @param string $prefix 路由前缀 + * @return $this + */ + public function prefix(string $prefix) + { + if ($this->parent && $this->parent->getOption('prefix')) { + $prefix = $this->parent->getOption('prefix') . $prefix; + } + + return $this->setOption('prefix', $prefix); + } + + /** + * 合并分组的路由规则正则 + * @access public + * @param bool $merge 是否合并 + * @return $this + */ + public function mergeRuleRegex(bool $merge = true) + { + return $this->setOption('merge_rule_regex', $merge); + } + + /** + * 获取完整分组Name + * @access public + * @return string + */ + public function getFullName(): string + { + return $this->fullName ?: ''; + } + + /** + * 获取分组的路由规则 + * @access public + * @param string $method 请求类型 + * @return array + */ + public function getRules(string $method = ''): array + { + if ('' === $method) { + return $this->rules; + } + + return $this->rules[strtolower($method)] ?? []; + } + + /** + * 清空分组下的路由规则 + * @access public + * @return void + */ + public function clear(): void + { + $this->rules = [ + '*' => [], + 'get' => [], + 'post' => [], + 'put' => [], + 'patch' => [], + 'delete' => [], + 'head' => [], + 'options' => [], + ]; + } +} diff --git a/vendor/topthink/framework/src/think/route/RuleItem.php b/vendor/topthink/framework/src/think/route/RuleItem.php new file mode 100644 index 0000000..f7f72ba --- /dev/null +++ b/vendor/topthink/framework/src/think/route/RuleItem.php @@ -0,0 +1,330 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\Exception; +use think\Request; +use think\Route; + +/** + * 路由规则类 + */ +class RuleItem extends Rule +{ + /** + * 是否为MISS规则 + * @var bool + */ + protected $miss = false; + + /** + * 是否为额外自动注册的OPTIONS规则 + * @var bool + */ + protected $autoOption = false; + + /** + * 架构函数 + * @access public + * @param Route $router 路由实例 + * @param RuleGroup $parent 上级对象 + * @param string $name 路由标识 + * @param string $rule 路由规则 + * @param string|\Closure $route 路由地址 + * @param string $method 请求类型 + */ + public function __construct(Route $router, RuleGroup $parent, string $name = null, string $rule = '', $route = null, string $method = '*') + { + $this->router = $router; + $this->parent = $parent; + $this->name = $name; + $this->route = $route; + $this->method = $method; + + $this->setRule($rule); + + $this->router->setRule($this->rule, $this); + } + + /** + * 设置当前路由规则为MISS路由 + * @access public + * @return $this + */ + public function setMiss() + { + $this->miss = true; + return $this; + } + + /** + * 判断当前路由规则是否为MISS路由 + * @access public + * @return bool + */ + public function isMiss(): bool + { + return $this->miss; + } + + /** + * 设置当前路由为自动注册OPTIONS + * @access public + * @return $this + */ + public function setAutoOptions() + { + $this->autoOption = true; + return $this; + } + + /** + * 判断当前路由规则是否为自动注册的OPTIONS路由 + * @access public + * @return bool + */ + public function isAutoOptions(): bool + { + return $this->autoOption; + } + + /** + * 获取当前路由的URL后缀 + * @access public + * @return string|null + */ + public function getSuffix() + { + if (isset($this->option['ext'])) { + $suffix = $this->option['ext']; + } elseif ($this->parent->getOption('ext')) { + $suffix = $this->parent->getOption('ext'); + } else { + $suffix = null; + } + + return $suffix; + } + + /** + * 路由规则预处理 + * @access public + * @param string $rule 路由规则 + * @return void + */ + public function setRule(string $rule): void + { + if ('$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $rule = substr($rule, 0, -1); + + $this->option['complete_match'] = true; + } + + $rule = '/' != $rule ? ltrim($rule, '/') : ''; + + if ($this->parent && $prefix = $this->parent->getFullName()) { + $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : ''); + } + + if (false !== strpos($rule, ':')) { + $this->rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule); + } else { + $this->rule = $rule; + } + + // 生成路由标识的快捷访问 + $this->setRuleName(); + } + + /** + * 设置别名 + * @access public + * @param string $name + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + $this->setRuleName(true); + + return $this; + } + + /** + * 设置路由标识 用于URL反解生成 + * @access protected + * @param bool $first 是否插入开头 + * @return void + */ + protected function setRuleName(bool $first = false): void + { + if ($this->name) { + $this->router->setName($this->name, $this, $first); + } + } + + /** + * 检测路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $match 匹配路由变量 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function checkRule(Request $request, string $url, $match = null, bool $completeMatch = false) + { + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + + // 合并分组参数 + $option = $this->mergeGroupOptions(); + + $url = $this->urlSuffixCheck($request, $url, $option); + + if (is_null($match)) { + $match = $this->match($url, $option, $completeMatch); + } + + if (false !== $match) { + return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); + } + + return false; + } + + /** + * 检测路由(含路由匹配) + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check(Request $request, string $url, bool $completeMatch = false) + { + return $this->checkRule($request, $url, null, $completeMatch); + } + + /** + * URL后缀及Slash检查 + * @access protected + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $option 路由参数 + * @return string + */ + protected function urlSuffixCheck(Request $request, string $url, array $option = []): string + { + // 是否区分 / 地址访问 + if (!empty($option['remove_slash']) && '/' != $this->rule) { + $this->rule = rtrim($this->rule, '/'); + $url = rtrim($url, '|'); + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); + } + + return $url; + } + + /** + * 检测URL和规则路由是否匹配 + * @access private + * @param string $url URL地址 + * @param array $option 路由参数 + * @param bool $completeMatch 路由是否完全匹配 + * @return array|false + */ + private function match(string $url, array $option, bool $completeMatch) + { + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; + } + + $depr = $this->router->config('pathinfo_depr'); + $pattern = array_merge($this->parent->getPattern(), $this->pattern); + + // 检查完整规则定义 + if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . ($completeMatch ? '$' : '') . '/', str_replace('|', $depr, $url))) { + return false; + } + + $var = []; + $url = $depr . str_replace('|', $depr, $url); + $rule = $depr . str_replace('/', $depr, $this->rule); + + if ($depr == $rule && $depr != $url) { + return false; + } + + if (false === strpos($rule, '<')) { + if (0 === strcasecmp($rule, $url) || (!$completeMatch && 0 === strncasecmp($rule . $depr, $url . $depr, strlen($rule . $depr)))) { + return $var; + } + return false; + } + + $slash = preg_quote('/-' . $depr, '/'); + + if ($matchRule = preg_split('/[' . $slash . ']?<\w+\??>/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + return false; + } + } + + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { + $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); + + try { + if (!preg_match('/^' . $regex . '/u', $url, $match)) { + return false; + } + } catch (\Exception $e) { + throw new Exception('route pattern error'); + } + + foreach ($match as $key => $val) { + if (is_string($key)) { + $var[$key] = $val; + } + } + } + + // 成功匹配后返回URL中的动态变量数组 + return $var; + } + + /** + * 设置路由所属分组(用于注解路由) + * @access public + * @param string $name 分组名称或者标识 + * @return $this + */ + public function group(string $name) + { + $group = $this->router->getRuleName()->getGroup($name); + + if ($group) { + $this->parent = $group; + $this->setRule($this->rule); + } + + return $this; + } +} diff --git a/vendor/topthink/framework/src/think/route/RuleName.php b/vendor/topthink/framework/src/think/route/RuleName.php new file mode 100644 index 0000000..9b1088a --- /dev/null +++ b/vendor/topthink/framework/src/think/route/RuleName.php @@ -0,0 +1,195 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +/** + * 路由标识管理类 + */ +class RuleName +{ + /** + * 路由标识 + * @var array + */ + protected $item = []; + + /** + * 路由规则 + * @var array + */ + protected $rule = []; + + /** + * 路由分组 + * @var array + */ + protected $group = []; + + /** + * 注册路由标识 + * @access public + * @param string $name 路由标识 + * @param RuleItem $ruleItem 路由规则 + * @param bool $first 是否优先 + * @return void + */ + public function setName(string $name, RuleItem $ruleItem, bool $first = false): void + { + $name = strtolower($name); + if ($first && isset($this->item[$name])) { + array_unshift($this->item[$name], $ruleItem); + } else { + $this->item[$name][] = $ruleItem; + } + } + + /** + * 注册路由分组标识 + * @access public + * @param string $name 路由分组标识 + * @param RuleGroup $group 路由分组 + * @return void + */ + public function setGroup(string $name, RuleGroup $group): void + { + $this->group[strtolower($name)] = $group; + } + + /** + * 注册路由规则 + * @access public + * @param string $rule 路由规则 + * @param RuleItem $ruleItem 路由 + * @return void + */ + public function setRule(string $rule, RuleItem $ruleItem): void + { + $route = $ruleItem->getRoute(); + + if (is_string($route)) { + $this->rule[$rule][$route] = $ruleItem; + } else { + $this->rule[$rule][] = $ruleItem; + } + } + + /** + * 根据路由规则获取路由对象(列表) + * @access public + * @param string $rule 路由标识 + * @return RuleItem[] + */ + public function getRule(string $rule): array + { + return $this->rule[$rule] ?? []; + } + + /** + * 根据路由分组标识获取分组 + * @access public + * @param string $name 路由分组标识 + * @return RuleGroup|null + */ + public function getGroup(string $name) + { + return $this->group[strtolower($name)] ?? null; + } + + /** + * 清空路由规则 + * @access public + * @return void + */ + public function clear(): void + { + $this->item = []; + $this->rule = []; + } + + /** + * 获取全部路由列表 + * @access public + * @return array + */ + public function getRuleList(): array + { + $list = []; + + foreach ($this->rule as $rule => $rules) { + foreach ($rules as $item) { + $val = []; + + foreach (['method', 'rule', 'name', 'route', 'domain', 'pattern', 'option'] as $param) { + $call = 'get' . $param; + $val[$param] = $item->$call(); + } + + if ($item->isMiss()) { + $val['rule'] .= ''; + } + + $list[] = $val; + } + } + + return $list; + } + + /** + * 导入路由标识 + * @access public + * @param array $item 路由标识 + * @return void + */ + public function import(array $item): void + { + $this->item = $item; + } + + /** + * 根据路由标识获取路由信息(用于URL生成) + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 + * @param string $method 请求类型 + * @return array + */ + public function getName(string $name = null, string $domain = null, string $method = '*'): array + { + if (is_null($name)) { + return $this->item; + } + + $name = strtolower($name); + $method = strtolower($method); + $result = []; + + if (isset($this->item[$name])) { + if (is_null($domain)) { + $result = $this->item[$name]; + } else { + foreach ($this->item[$name] as $item) { + $itemDomain = $item->getDomain(); + $itemMethod = $item->getMethod(); + + if (($itemDomain == $domain || '-' == $itemDomain) && ('*' == $itemMethod || '*' == $method || $method == $itemMethod)) { + $result[] = $item; + } + } + } + } + + return $result; + } + +} diff --git a/vendor/topthink/framework/src/think/route/Url.php b/vendor/topthink/framework/src/think/route/Url.php new file mode 100644 index 0000000..d9ecfd8 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Url.php @@ -0,0 +1,512 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\App; +use think\Route; + +/** + * 路由地址生成 + */ +class Url +{ + /** + * 应用对象 + * @var App + */ + protected $app; + + /** + * 路由对象 + * @var Route + */ + protected $route; + + /** + * URL变量 + * @var array + */ + protected $vars = []; + + /** + * 路由URL + * @var string + */ + protected $url; + + /** + * URL 根地址 + * @var string + */ + protected $root = ''; + + /** + * HTTPS + * @var bool + */ + protected $https; + + /** + * URL后缀 + * @var string|bool + */ + protected $suffix = true; + + /** + * URL域名 + * @var string|bool + */ + protected $domain = false; + + /** + * 架构函数 + * @access public + * @param string $url URL地址 + * @param array $vars 参数 + */ + public function __construct(Route $route, App $app, string $url = '', array $vars = []) + { + $this->route = $route; + $this->app = $app; + $this->url = $url; + $this->vars = $vars; + } + + /** + * 设置URL参数 + * @access public + * @param array $vars URL参数 + * @return $this + */ + public function vars(array $vars = []) + { + $this->vars = $vars; + return $this; + } + + /** + * 设置URL后缀 + * @access public + * @param string|bool $suffix URL后缀 + * @return $this + */ + public function suffix($suffix) + { + $this->suffix = $suffix; + return $this; + } + + /** + * 设置URL域名(或者子域名) + * @access public + * @param string|bool $domain URL域名 + * @return $this + */ + public function domain($domain) + { + $this->domain = $domain; + return $this; + } + + /** + * 设置URL 根地址 + * @access public + * @param string $root URL root + * @return $this + */ + public function root(string $root) + { + $this->root = $root; + return $this; + } + + /** + * 设置是否使用HTTPS + * @access public + * @param bool $https + * @return $this + */ + public function https(bool $https = true) + { + $this->https = $https; + return $this; + } + + /** + * 检测域名 + * @access protected + * @param string $url URL + * @param string|true $domain 域名 + * @return string + */ + protected function parseDomain(string &$url, $domain): string + { + if (!$domain) { + return ''; + } + + $request = $this->app->request; + $rootDomain = $request->rootDomain(); + + if (true === $domain) { + // 自动判断域名 + $domain = $request->host(); + $domains = $this->route->getDomains(); + + if (!empty($domains)) { + $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; + } + } + } + } + } + } elseif (false === strpos($domain, '.') && 0 !== strpos($domain, $rootDomain)) { + $domain .= '.' . $rootDomain; + } + + if (false !== strpos($domain, '://')) { + $scheme = ''; + } else { + $scheme = $this->https || $request->isSsl() ? 'https://' : 'http://'; + } + + return $scheme . $domain; + } + + /** + * 解析URL后缀 + * @access protected + * @param string|bool $suffix 后缀 + * @return string + */ + protected function parseSuffix($suffix): string + { + if ($suffix) { + $suffix = true === $suffix ? $this->route->config('url_html_suffix') : $suffix; + + if (is_string($suffix) && $pos = strpos($suffix, '|')) { + $suffix = substr($suffix, 0, $pos); + } + } + + return (empty($suffix) || 0 === strpos($suffix, '.')) ? (string) $suffix : '.' . $suffix; + } + + /** + * 直接解析URL地址 + * @access protected + * @param string $url URL + * @param string|bool $domain Domain + * @return string + */ + protected function parseUrl(string $url, &$domain): string + { + $request = $this->app->request; + + if (0 === strpos($url, '/')) { + // 直接作为路由地址解析 + $url = substr($url, 1); + } elseif (false !== strpos($url, '\\')) { + // 解析到类 + $url = ltrim(str_replace('\\', '/', $url), '/'); + } elseif (0 === strpos($url, '@')) { + // 解析到控制器 + $url = substr($url, 1); + } elseif ('' === $url) { + $url = $request->controller() . '/' . $request->action(); + } else { + $controller = $request->controller(); + + $path = explode('/', $url); + $action = array_pop($path); + $controller = empty($path) ? $controller : array_pop($path); + + $url = $controller . '/' . $action; + } + + return $url; + } + + /** + * 分析路由规则中的变量 + * @access protected + * @param string $rule 路由规则 + * @return array + */ + protected function parseVar(string $rule): array + { + // 提取路由规则中的变量 + $var = []; + + if (preg_match_all('/<\w+\??>/', $rule, $matches)) { + foreach ($matches[0] as $name) { + $optional = false; + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = true; + } else { + $name = substr($name, 1, -1); + } + + $var[$name] = $optional ? 2 : 1; + } + } + + return $var; + } + + /** + * 匹配路由地址 + * @access protected + * @param array $rule 路由规则 + * @param array $vars 路由变量 + * @param mixed $allowDomain 允许域名 + * @return array + */ + protected function getRuleUrl(array $rule, array &$vars = [], $allowDomain = ''): array + { + $request = $this->app->request; + if (is_string($allowDomain) && false === strpos($allowDomain, '.')) { + $allowDomain .= '.' . $request->rootDomain(); + } + $port = $request->port(); + + foreach ($rule as $item) { + $url = $item->getRule(); + $pattern = $this->parseVar($url); + $domain = $item->getDomain(); + $suffix = $item->getSuffix(); + + if ('-' == $domain) { + $domain = is_string($allowDomain) ? $allowDomain : $request->host(true); + } + + if (is_string($allowDomain) && $domain != $allowDomain) { + continue; + } + + if ($port && !in_array($port, [80, 443])) { + $domain .= ':' . $port; + } + + if (empty($pattern)) { + return [rtrim($url, '?/-'), $domain, $suffix]; + } + + $type = $this->route->config('url_common_param'); + $keys = []; + + foreach ($pattern as $key => $val) { + if (isset($vars[$key])) { + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? $vars[$key] : urlencode((string) $vars[$key]), $url); + $keys[] = $key; + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; + } elseif (2 == $val) { + $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; + } else { + $result = null; + $keys = []; + break; + } + } + + $vars = array_diff_key($vars, array_flip($keys)); + + if (isset($result)) { + return $result; + } + } + + return []; + } + + public function build() + { + // 解析URL + $url = $this->url; + $suffix = $this->suffix; + $domain = $this->domain; + $request = $this->app->request; + $vars = $this->vars; + + if (0 === strpos($url, '[') && $pos = strpos($url, ']')) { + // [name] 表示使用路由命名标识生成URL + $name = substr($url, 1, $pos - 1); + $url = 'name' . substr($url, $pos + 1); + } + + if (false === strpos($url, '://') && 0 !== strpos($url, '/')) { + $info = parse_url($url); + $url = !empty($info['path']) ? $info['path'] : ''; + + if (isset($info['fragment'])) { + // 解析锚点 + $anchor = $info['fragment']; + + if (false !== strpos($anchor, '?')) { + // 解析参数 + [$anchor, $info['query']] = explode('?', $anchor, 2); + } + + if (false !== strpos($anchor, '@')) { + // 解析域名 + [$anchor, $domain] = explode('@', $anchor, 2); + } + } elseif (strpos($url, '@') && false === strpos($url, '\\')) { + // 解析域名 + [$url, $domain] = explode('@', $url, 2); + } + } + + if ($url) { + $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : ''); + $checkDomain = $domain && is_string($domain) ? $domain : null; + + $rule = $this->route->getName($checkName, $checkDomain); + + if (empty($rule) && isset($info['query'])) { + $rule = $this->route->getName($url, $checkDomain); + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + unset($info['query']); + } + } + + if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) { + // 匹配路由命名标识 + $url = $match[0]; + + if ($domain && !empty($match[1])) { + $domain = $match[1]; + } + + if (!is_null($match[2])) { + $suffix = $match[2]; + } + } elseif (!empty($rule) && isset($name)) { + throw new \InvalidArgumentException('route name not exists:' . $name); + } else { + // 检测URL绑定 + $bind = $this->route->getDomainBind($domain && is_string($domain) ? $domain : null); + + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } else { + $binds = $this->route->getBind(); + + foreach ($binds as $key => $val) { + if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) { + $url = substr($url, strlen($val) + 1); + $domain = $key; + break; + } + } + } + + // 路由标识不存在 直接解析 + $url = $this->parseUrl($url, $domain); + + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + } + } + + // 还原URL分隔符 + $depr = $this->route->config('pathinfo_depr'); + $url = str_replace('/', $depr, $url); + + $file = $request->baseFile(); + if ($file && 0 !== strpos($request->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + + $url = rtrim($file, '/') . '/' . $url; + + // URL后缀 + if ('/' == substr($url, -1) || '' == $url) { + $suffix = ''; + } else { + $suffix = $this->parseSuffix($suffix); + } + + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; + + // 参数组装 + if (!empty($vars)) { + // 添加参数 + if ($this->route->config('url_common_param')) { + $vars = http_build_query($vars); + $url .= $suffix . '?' . $vars . $anchor; + } else { + foreach ($vars as $var => $val) { + $val = (string) $val; + if ('' !== $val) { + $url .= $depr . $var . $depr . urlencode($val); + } + } + + $url .= $suffix . $anchor; + } + } else { + $url .= $suffix . $anchor; + } + + // 检测域名 + $domain = $this->parseDomain($url, $domain); + + // URL组装 + return $domain . rtrim($this->root, '/') . '/' . ltrim($url, '/'); + } + + public function __toString() + { + return $this->build(); + } + + public function __debugInfo() + { + return [ + 'url' => $this->url, + 'vars' => $this->vars, + 'suffix' => $this->suffix, + 'domain' => $this->domain, + ]; + } +} diff --git a/vendor/topthink/framework/src/think/route/dispatch/Callback.php b/vendor/topthink/framework/src/think/route/dispatch/Callback.php new file mode 100644 index 0000000..7658c3d --- /dev/null +++ b/vendor/topthink/framework/src/think/route/dispatch/Callback.php @@ -0,0 +1,30 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route\dispatch; + +use think\route\Dispatch; + +/** + * Callback Dispatcher + */ +class Callback extends Dispatch +{ + public function exec() + { + // 执行回调方法 + $vars = array_merge($this->request->param(), $this->param); + + return $this->app->invoke($this->dispatch, $vars); + } + +} diff --git a/vendor/topthink/framework/src/think/route/dispatch/Controller.php b/vendor/topthink/framework/src/think/route/dispatch/Controller.php new file mode 100644 index 0000000..85aadf8 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/dispatch/Controller.php @@ -0,0 +1,176 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route\dispatch; + +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use think\App; +use think\exception\ClassNotFoundException; +use think\exception\HttpException; +use think\helper\Str; +use think\route\Dispatch; + +/** + * Controller Dispatcher + */ +class Controller extends Dispatch +{ + /** + * 控制器名 + * @var string + */ + protected $controller; + + /** + * 操作名 + * @var string + */ + protected $actionName; + + public function init(App $app) + { + parent::init($app); + + $result = $this->dispatch; + + if (is_string($result)) { + $result = explode('/', $result); + } + + // 获取控制器名 + $controller = strip_tags($result[0] ?: $this->rule->config('default_controller')); + + if (strpos($controller, '.')) { + $pos = strrpos($controller, '.'); + $this->controller = substr($controller, 0, $pos) . '.' . Str::studly(substr($controller, $pos + 1)); + } else { + $this->controller = Str::studly($controller); + } + + // 获取操作名 + $this->actionName = strip_tags($result[1] ?: $this->rule->config('default_action')); + + // 设置当前请求的控制器、操作 + $this->request + ->setController($this->controller) + ->setAction($this->actionName); + } + + public function exec() + { + try { + // 实例化控制器 + $instance = $this->controller($this->controller); + } catch (ClassNotFoundException $e) { + throw new HttpException(404, 'controller not exists:' . $e->getClass()); + } + + // 注册控制器中间件 + $this->registerControllerMiddleware($instance); + + return $this->app->middleware->pipeline('controller') + ->send($this->request) + ->then(function () use ($instance) { + // 获取当前操作名 + $action = $this->actionName . $this->rule->config('action_suffix'); + + if (is_callable([$instance, $action])) { + $vars = $this->request->param(); + try { + $reflect = new ReflectionMethod($instance, $action); + // 严格获取当前操作方法名 + $actionName = $reflect->getName(); + $this->request->setAction($actionName); + } catch (ReflectionException $e) { + $reflect = new ReflectionMethod($instance, '__call'); + $vars = [$action, $vars]; + $this->request->setAction($action); + } + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); + } + + $data = $this->app->invokeReflectMethod($instance, $reflect, $vars); + + return $this->autoResponse($data); + }); + } + + /** + * 使用反射机制注册控制器中间件 + * @access public + * @param object $controller 控制器实例 + * @return void + */ + protected function registerControllerMiddleware($controller): void + { + $class = new ReflectionClass($controller); + + if ($class->hasProperty('middleware')) { + $reflectionProperty = $class->getProperty('middleware'); + $reflectionProperty->setAccessible(true); + + $middlewares = $reflectionProperty->getValue($controller); + + foreach ($middlewares as $key => $val) { + if (!is_int($key)) { + if (isset($val['only']) && !in_array($this->request->action(true), array_map(function ($item) { + return strtolower($item); + }, is_string($val['only']) ? explode(",", $val['only']) : $val['only']))) { + continue; + } elseif (isset($val['except']) && in_array($this->request->action(true), array_map(function ($item) { + return strtolower($item); + }, is_string($val['except']) ? explode(',', $val['except']) : $val['except']))) { + continue; + } else { + $val = $key; + } + } + + if (is_string($val) && strpos($val, ':')) { + $val = explode(':', $val, 2); + } + + $this->app->middleware->controller($val); + } + } + } + + /** + * 实例化访问控制器 + * @access public + * @param string $name 资源地址 + * @return object + * @throws ClassNotFoundException + */ + public function controller(string $name) + { + $suffix = $this->rule->config('controller_suffix') ? 'Controller' : ''; + + $controllerLayer = $this->rule->config('controller_layer') ?: 'controller'; + $emptyController = $this->rule->config('empty_controller') ?: 'Error'; + + + $class = $this->app->parseClass($controllerLayer, $name . $suffix); + + if (class_exists($class)) { + return $this->app->make($class, [], true); + } elseif ($emptyController && class_exists($emptyClass = $this->app->parseClass($controllerLayer, $emptyController . $suffix))) { + return $this->app->make($emptyClass, [], true); + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } +} diff --git a/vendor/topthink/framework/src/think/route/dispatch/Redirect.php b/vendor/topthink/framework/src/think/route/dispatch/Redirect.php new file mode 100644 index 0000000..1ec9ed9 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/dispatch/Redirect.php @@ -0,0 +1,27 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route\dispatch; + +use think\Response; +use think\route\Dispatch; + +/** + * Redirect Dispatcher + */ +class Redirect extends Dispatch +{ + public function exec() + { + return Response::create($this->dispatch, 'redirect')->code($this->code); + } +} diff --git a/vendor/topthink/framework/src/think/route/dispatch/Response.php b/vendor/topthink/framework/src/think/route/dispatch/Response.php new file mode 100644 index 0000000..3ae4c0a --- /dev/null +++ b/vendor/topthink/framework/src/think/route/dispatch/Response.php @@ -0,0 +1,27 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route\dispatch; + +use think\route\Dispatch; + +/** + * Response Dispatcher + */ +class Response extends Dispatch +{ + public function exec() + { + return $this->dispatch; + } + +} diff --git a/vendor/topthink/framework/src/think/route/dispatch/Url.php b/vendor/topthink/framework/src/think/route/dispatch/Url.php new file mode 100644 index 0000000..da6d655 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/dispatch/Url.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route\dispatch; + +use think\exception\HttpException; +use think\helper\Str; +use think\Request; +use think\route\Rule; + +/** + * Url Dispatcher + */ +class Url extends Controller +{ + + public function __construct(Request $request, Rule $rule, $dispatch, array $param = [], int $code = null) + { + $this->request = $request; + $this->rule = $rule; + // 解析默认的URL规则 + $dispatch = $this->parseUrl($dispatch); + + parent::__construct($request, $rule, $dispatch, $this->param, $code); + } + + /** + * 解析URL地址 + * @access protected + * @param string $url URL + * @return array + */ + protected function parseUrl(string $url): array + { + $depr = $this->rule->config('pathinfo_depr'); + $bind = $this->rule->getRouter()->getDomainBind(); + + if ($bind && preg_match('/^[a-z]/is', $bind)) { + $bind = str_replace('/', $depr, $bind); + // 如果有域名绑定 + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); + } + + $path = $this->rule->parseUrlPath($url); + if (empty($path)) { + return [null, null]; + } + + // 解析控制器 + $controller = !empty($path) ? array_shift($path) : null; + + if ($controller && !preg_match('/^[A-Za-z0-9][\w|\.]*$/', $controller)) { + throw new HttpException(404, 'controller not exists:' . $controller); + } + + // 解析操作 + $action = !empty($path) ? array_shift($path) : null; + $var = []; + + // 解析额外参数 + if ($path) { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, implode('|', $path)); + } + + $panDomain = $this->request->panDomain(); + if ($panDomain && $key = array_search('*', $var)) { + // 泛域名赋值 + $var[$key] = $panDomain; + } + + // 设置当前请求的参数 + $this->param = $var; + + // 封装路由 + $route = [$controller, $action]; + + if ($this->hasDefinedRoute($route)) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); + } + + return $route; + } + + /** + * 检查URL是否已经定义过路由 + * @access protected + * @param array $route 路由信息 + * @return bool + */ + protected function hasDefinedRoute(array $route): bool + { + [$controller, $action] = $route; + + // 检查地址是否被定义过路由 + $name = strtolower(Str::studly($controller) . '/' . $action); + + $host = $this->request->host(true); + $method = $this->request->method(); + + if ($this->rule->getRouter()->getName($name, $host, $method)) { + return true; + } + + return false; + } + +} diff --git a/vendor/topthink/framework/src/think/route/dispatch/View.php b/vendor/topthink/framework/src/think/route/dispatch/View.php new file mode 100644 index 0000000..94f9047 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/dispatch/View.php @@ -0,0 +1,28 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route\dispatch; + +use think\Response; +use think\route\Dispatch; + +/** + * View Dispatcher + */ +class View extends Dispatch +{ + public function exec() + { + // 渲染模板输出 + return Response::create($this->dispatch, 'view')->assign($this->param); + } +} diff --git a/vendor/topthink/framework/src/think/service/ModelService.php b/vendor/topthink/framework/src/think/service/ModelService.php new file mode 100644 index 0000000..a212a58 --- /dev/null +++ b/vendor/topthink/framework/src/think/service/ModelService.php @@ -0,0 +1,47 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\service; + +use think\Model; +use think\Service; + +/** + * 模型服务类 + */ +class ModelService extends Service +{ + public function boot() + { + Model::setDb($this->app->db); + Model::setEvent($this->app->event); + Model::setInvoker([$this->app, 'invoke']); + Model::maker(function (Model $model) { + $config = $this->app->config; + + $isAutoWriteTimestamp = $model->getAutoWriteTimestamp(); + + if (is_null($isAutoWriteTimestamp)) { + // 自动写入时间戳 + $model->isAutoWriteTimestamp($config->get('database.auto_timestamp', 'timestamp')); + } + + $dateFormat = $model->getDateFormat(); + + if (is_null($dateFormat)) { + // 设置时间戳格式 + $model->setDateFormat($config->get('database.datetime_format', 'Y-m-d H:i:s')); + } + + }); + } +} diff --git a/vendor/topthink/framework/src/think/service/PaginatorService.php b/vendor/topthink/framework/src/think/service/PaginatorService.php new file mode 100644 index 0000000..b13dc1d --- /dev/null +++ b/vendor/topthink/framework/src/think/service/PaginatorService.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\service; + +use think\Paginator; +use think\paginator\driver\Bootstrap; +use think\Service; + +/** + * 分页服务类 + */ +class PaginatorService extends Service +{ + public function register() + { + if (!$this->app->bound(Paginator::class)) { + $this->app->bind(Paginator::class, Bootstrap::class); + } + } + + public function boot() + { + Paginator::maker(function (...$args) { + return $this->app->make(Paginator::class, $args, true); + }); + + Paginator::currentPathResolver(function () { + return $this->app->request->baseUrl(); + }); + + Paginator::currentPageResolver(function ($varPage = 'page') { + + $page = $this->app->request->param($varPage); + + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { + return (int) $page; + } + + return 1; + }); + } +} diff --git a/vendor/topthink/framework/src/think/service/ValidateService.php b/vendor/topthink/framework/src/think/service/ValidateService.php new file mode 100644 index 0000000..d96dea0 --- /dev/null +++ b/vendor/topthink/framework/src/think/service/ValidateService.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\service; + +use think\Service; +use think\Validate; + +/** + * 验证服务类 + */ +class ValidateService extends Service +{ + public function boot() + { + Validate::maker(function (Validate $validate) { + $validate->setLang($this->app->lang); + $validate->setDb($this->app->db); + $validate->setRequest($this->app->request); + }); + } +} diff --git a/vendor/topthink/framework/src/think/session/Store.php b/vendor/topthink/framework/src/think/session/Store.php new file mode 100644 index 0000000..30f4630 --- /dev/null +++ b/vendor/topthink/framework/src/think/session/Store.php @@ -0,0 +1,340 @@ + +// +---------------------------------------------------------------------- + +namespace think\session; + +use think\contract\SessionHandlerInterface; +use think\helper\Arr; + +class Store +{ + + /** + * Session数据 + * @var array + */ + protected $data = []; + + /** + * 是否初始化 + * @var bool + */ + protected $init = null; + + /** + * 记录Session name + * @var string + */ + protected $name = 'PHPSESSID'; + + /** + * 记录Session Id + * @var string + */ + protected $id; + + /** + * @var SessionHandlerInterface + */ + protected $handler; + + /** @var array */ + protected $serialize = []; + + public function __construct($name, SessionHandlerInterface $handler, array $serialize = null) + { + $this->name = $name; + $this->handler = $handler; + + if (!empty($serialize)) { + $this->serialize = $serialize; + } + + $this->setId(); + } + + /** + * 设置数据 + * @access public + * @param array $data + * @return void + */ + public function setData(array $data): void + { + $this->data = $data; + } + + /** + * session初始化 + * @access public + * @return void + */ + public function init(): void + { + // 读取缓存数据 + $data = $this->handler->read($this->getId()); + + if (!empty($data)) { + $this->data = array_merge($this->data, $this->unserialize($data)); + } + + $this->init = true; + } + + /** + * 设置SessionName + * @access public + * @param string $name session_name + * @return void + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * 获取sessionName + * @access public + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * session_id设置 + * @access public + * @param string $id session_id + * @return void + */ + public function setId($id = null): void + { + $this->id = is_string($id) && strlen($id) === 32 ? $id : md5(microtime(true) . session_create_id()); + } + + /** + * 获取session_id + * @access public + * @return string + */ + public function getId(): string + { + return $this->id; + } + + /** + * 获取所有数据 + * @return array + */ + public function all(): array + { + return $this->data; + } + + /** + * session设置 + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @return void + */ + public function set(string $name, $value): void + { + Arr::set($this->data, $name, $value); + } + + /** + * session获取 + * @access public + * @param string $name session名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function get(string $name, $default = null) + { + return Arr::get($this->data, $name, $default); + } + + /** + * session获取并删除 + * @access public + * @param string $name session名称 + * @return mixed + */ + public function pull(string $name) + { + return Arr::pull($this->data, $name); + } + + /** + * 添加数据到一个session数组 + * @access public + * @param string $key + * @param mixed $value + * @return void + */ + public function push(string $key, $value): void + { + $array = $this->get($key, []); + + $array[] = $value; + + $this->set($key, $array); + } + + /** + * 判断session数据 + * @access public + * @param string $name session名称 + * @return bool + */ + public function has(string $name): bool + { + return Arr::has($this->data, $name); + } + + /** + * 删除session数据 + * @access public + * @param string $name session名称 + * @return void + */ + public function delete(string $name): void + { + Arr::forget($this->data, $name); + } + + /** + * 清空session数据 + * @access public + * @return void + */ + public function clear(): void + { + $this->data = []; + } + + /** + * 销毁session + */ + public function destroy(): void + { + $this->clear(); + + $this->regenerate(true); + } + + /** + * 重新生成session id + * @param bool $destroy + */ + public function regenerate(bool $destroy = false): void + { + if ($destroy) { + $this->handler->delete($this->getId()); + } + + $this->setId(); + } + + /** + * 保存session数据 + * @access public + * @return void + */ + public function save(): void + { + $this->clearFlashData(); + + $sessionId = $this->getId(); + + if (!empty($this->data)) { + $data = $this->serialize($this->data); + + $this->handler->write($sessionId, $data); + } else { + $this->handler->delete($sessionId); + } + + $this->init = false; + } + + /** + * session设置 下一次请求有效 + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @return void + */ + public function flash(string $name, $value): void + { + $this->set($name, $value); + $this->push('__flash__.__next__', $name); + $this->set('__flash__.__current__', Arr::except($this->get('__flash__.__current__', []), $name)); + } + + /** + * 将本次闪存数据推迟到下次请求 + * + * @return void + */ + public function reflash(): void + { + $keys = $this->get('__flash__.__current__', []); + $values = array_unique(array_merge($this->get('__flash__.__next__', []), $keys)); + $this->set('__flash__.__next__', $values); + $this->set('__flash__.__current__', []); + } + + /** + * 清空当前请求的session数据 + * @access public + * @return void + */ + public function clearFlashData(): void + { + Arr::forget($this->data, $this->get('__flash__.__current__', [])); + if (!empty($next = $this->get('__flash__.__next__', []))) { + $this->set('__flash__.__current__', $next); + } else { + $this->delete('__flash__.__current__'); + } + $this->delete('__flash__.__next__'); + } + + /** + * 序列化数据 + * @access protected + * @param mixed $data + * @return string + */ + protected function serialize($data): string + { + $serialize = $this->serialize[0] ?? 'serialize'; + + return $serialize($data); + } + + /** + * 反序列化数据 + * @access protected + * @param string $data + * @return array + */ + protected function unserialize(string $data): array + { + $unserialize = $this->serialize[1] ?? 'unserialize'; + + return (array) $unserialize($data); + } + +} diff --git a/vendor/topthink/framework/src/think/session/driver/Cache.php b/vendor/topthink/framework/src/think/session/driver/Cache.php new file mode 100644 index 0000000..c25e2ef --- /dev/null +++ b/vendor/topthink/framework/src/think/session/driver/Cache.php @@ -0,0 +1,50 @@ + +// +---------------------------------------------------------------------- +namespace think\session\driver; + +use Psr\SimpleCache\CacheInterface; +use think\contract\SessionHandlerInterface; +use think\helper\Arr; + +class Cache implements SessionHandlerInterface +{ + + /** @var CacheInterface */ + protected $handler; + + /** @var integer */ + protected $expire; + + /** @var string */ + protected $prefix; + + public function __construct(\think\Cache $cache, array $config = []) + { + $this->handler = $cache->store(Arr::get($config, 'store')); + $this->expire = Arr::get($config, 'expire', 1440); + $this->prefix = Arr::get($config, 'prefix', ''); + } + + public function read(string $sessionId): string + { + return (string) $this->handler->get($this->prefix . $sessionId); + } + + public function delete(string $sessionId): bool + { + return $this->handler->delete($this->prefix . $sessionId); + } + + public function write(string $sessionId, string $data): bool + { + return $this->handler->set($this->prefix . $sessionId, $data, $this->expire); + } +} diff --git a/vendor/topthink/framework/src/think/session/driver/File.php b/vendor/topthink/framework/src/think/session/driver/File.php new file mode 100644 index 0000000..991f2cc --- /dev/null +++ b/vendor/topthink/framework/src/think/session/driver/File.php @@ -0,0 +1,249 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\session\driver; + +use Closure; +use Exception; +use FilesystemIterator; +use Generator; +use SplFileInfo; +use think\App; +use think\contract\SessionHandlerInterface; + +/** + * Session 文件驱动 + */ +class File implements SessionHandlerInterface +{ + protected $config = [ + 'path' => '', + 'expire' => 1440, + 'prefix' => '', + 'data_compress' => false, + 'gc_probability' => 1, + 'gc_divisor' => 100, + ]; + + public function __construct(App $app, array $config = []) + { + $this->config = array_merge($this->config, $config); + + if (empty($this->config['path'])) { + $this->config['path'] = $app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . 'session' . DIRECTORY_SEPARATOR; + } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) { + $this->config['path'] .= DIRECTORY_SEPARATOR; + } + + $this->init(); + } + + /** + * 打开Session + * @access protected + * @throws Exception + */ + protected function init(): void + { + try { + !is_dir($this->config['path']) && mkdir($this->config['path'], 0755, true); + } catch (\Exception $e) { + // 写入失败 + } + + // 垃圾回收 + if (random_int(1, $this->config['gc_divisor']) <= $this->config['gc_probability']) { + $this->gc(); + } + } + + /** + * Session 垃圾回收 + * @access public + * @return void + */ + public function gc(): void + { + $lifetime = $this->config['expire']; + $now = time(); + + $files = $this->findFiles($this->config['path'], function (SplFileInfo $item) use ($lifetime, $now) { + return $now - $lifetime > $item->getMTime(); + }); + + foreach ($files as $file) { + $this->unlink($file->getPathname()); + } + } + + /** + * 查找文件 + * @param string $root + * @param Closure $filter + * @return Generator + */ + protected function findFiles(string $root, Closure $filter) + { + $items = new FilesystemIterator($root); + + /** @var SplFileInfo $item */ + foreach ($items as $item) { + if ($item->isDir() && !$item->isLink()) { + yield from $this->findFiles($item->getPathname(), $filter); + } else { + if ($filter($item)) { + yield $item; + } + } + } + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @param bool $auto 是否自动创建目录 + * @return string + */ + protected function getFileName(string $name, bool $auto = false): string + { + if ($this->config['prefix']) { + // 使用子目录 + $name = $this->config['prefix'] . DIRECTORY_SEPARATOR . 'sess_' . $name; + } else { + $name = 'sess_' . $name; + } + + $filename = $this->config['path'] . $name; + $dir = dirname($filename); + + if ($auto && !is_dir($dir)) { + try { + mkdir($dir, 0755, true); + } catch (\Exception $e) { + // 创建失败 + } + } + + return $filename; + } + + /** + * 读取Session + * @access public + * @param string $sessID + * @return string + */ + public function read(string $sessID): string + { + $filename = $this->getFileName($sessID); + + if (is_file($filename) && filemtime($filename) >= time() - $this->config['expire']) { + $content = $this->readFile($filename); + + if ($this->config['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = (string) gzuncompress($content); + } + + return $content; + } + + return ''; + } + + /** + * 写文件(加锁) + * @param $path + * @param $content + * @return bool + */ + protected function writeFile($path, $content): bool + { + return (bool) file_put_contents($path, $content, LOCK_EX); + } + + /** + * 读取文件内容(加锁) + * @param $path + * @return string + */ + protected function readFile($path): string + { + $contents = ''; + + $handle = fopen($path, 'rb'); + + if ($handle) { + try { + if (flock($handle, LOCK_SH)) { + clearstatcache(true, $path); + + $contents = fread($handle, filesize($path) ?: 1); + + flock($handle, LOCK_UN); + } + } finally { + fclose($handle); + } + } + + return $contents; + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param string $sessData + * @return bool + */ + public function write(string $sessID, string $sessData): bool + { + $filename = $this->getFileName($sessID, true); + $data = $sessData; + + if ($this->config['data_compress'] && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data, 3); + } + + return $this->writeFile($filename, $data); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function delete(string $sessID): bool + { + try { + return $this->unlink($this->getFileName($sessID)); + } catch (\Exception $e) { + return false; + } + } + + /** + * 判断文件是否存在后,删除 + * @access private + * @param string $file + * @return bool + */ + private function unlink(string $file): bool + { + return is_file($file) && unlink($file); + } + +} diff --git a/vendor/topthink/framework/src/think/validate/ValidateRule.php b/vendor/topthink/framework/src/think/validate/ValidateRule.php new file mode 100644 index 0000000..05e275f --- /dev/null +++ b/vendor/topthink/framework/src/think/validate/ValidateRule.php @@ -0,0 +1,172 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\validate; + +/** + * Class ValidateRule + * @package think\validate + * @method ValidateRule confirm(mixed $rule, string $msg = '') static 验证是否和某个字段的值一致 + * @method ValidateRule different(mixed $rule, string $msg = '') static 验证是否和某个字段的值是否不同 + * @method ValidateRule egt(mixed $rule, string $msg = '') static 验证是否大于等于某个值 + * @method ValidateRule gt(mixed $rule, string $msg = '') static 验证是否大于某个值 + * @method ValidateRule elt(mixed $rule, string $msg = '') static 验证是否小于等于某个值 + * @method ValidateRule lt(mixed $rule, string $msg = '') static 验证是否小于某个值 + * @method ValidateRule eg(mixed $rule, string $msg = '') static 验证是否等于某个值 + * @method ValidateRule in(mixed $rule, string $msg = '') static 验证是否在范围内 + * @method ValidateRule notIn(mixed $rule, string $msg = '') static 验证是否不在某个范围 + * @method ValidateRule between(mixed $rule, string $msg = '') static 验证是否在某个区间 + * @method ValidateRule notBetween(mixed $rule, string $msg = '') static 验证是否不在某个区间 + * @method ValidateRule length(mixed $rule, string $msg = '') static 验证数据长度 + * @method ValidateRule max(mixed $rule, string $msg = '') static 验证数据最大长度 + * @method ValidateRule min(mixed $rule, string $msg = '') static 验证数据最小长度 + * @method ValidateRule after(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule before(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule expire(mixed $rule, string $msg = '') static 验证有效期 + * @method ValidateRule allowIp(mixed $rule, string $msg = '') static 验证IP许可 + * @method ValidateRule denyIp(mixed $rule, string $msg = '') static 验证IP禁用 + * @method ValidateRule regex(mixed $rule, string $msg = '') static 使用正则验证数据 + * @method ValidateRule token(mixed $rule='__token__', string $msg = '') static 验证表单令牌 + * @method ValidateRule is(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isRequire(mixed $rule = null, string $msg = '') static 验证字段必须 + * @method ValidateRule isNumber(mixed $rule = null, string $msg = '') static 验证字段值是否为数字 + * @method ValidateRule isArray(mixed $rule = null, string $msg = '') static 验证字段值是否为数组 + * @method ValidateRule isInteger(mixed $rule = null, string $msg = '') static 验证字段值是否为整形 + * @method ValidateRule isFloat(mixed $rule = null, string $msg = '') static 验证字段值是否为浮点数 + * @method ValidateRule isMobile(mixed $rule = null, string $msg = '') static 验证字段值是否为手机 + * @method ValidateRule isIdCard(mixed $rule = null, string $msg = '') static 验证字段值是否为身份证号码 + * @method ValidateRule isChs(mixed $rule = null, string $msg = '') static 验证字段值是否为中文 + * @method ValidateRule isChsDash(mixed $rule = null, string $msg = '') static 验证字段值是否为中文字母及下划线 + * @method ValidateRule isChsAlpha(mixed $rule = null, string $msg = '') static 验证字段值是否为中文和字母 + * @method ValidateRule isChsAlphaNum(mixed $rule = null, string $msg = '') static 验证字段值是否为中文字母和数字 + * @method ValidateRule isDate(mixed $rule = null, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isBool(mixed $rule = null, string $msg = '') static 验证字段值是否为布尔值 + * @method ValidateRule isAlpha(mixed $rule = null, string $msg = '') static 验证字段值是否为字母 + * @method ValidateRule isAlphaDash(mixed $rule = null, string $msg = '') static 验证字段值是否为字母和下划线 + * @method ValidateRule isAlphaNum(mixed $rule = null, string $msg = '') static 验证字段值是否为字母和数字 + * @method ValidateRule isAccepted(mixed $rule = null, string $msg = '') static 验证字段值是否为yes, on, 或是 1 + * @method ValidateRule isEmail(mixed $rule = null, string $msg = '') static 验证字段值是否为有效邮箱格式 + * @method ValidateRule isUrl(mixed $rule = null, string $msg = '') static 验证字段值是否为有效URL地址 + * @method ValidateRule activeUrl(mixed $rule, string $msg = '') static 验证是否为合格的域名或者IP + * @method ValidateRule ip(mixed $rule, string $msg = '') static 验证是否有效IP + * @method ValidateRule fileExt(mixed $rule, string $msg = '') static 验证文件后缀 + * @method ValidateRule fileMime(mixed $rule, string $msg = '') static 验证文件类型 + * @method ValidateRule fileSize(mixed $rule, string $msg = '') static 验证文件大小 + * @method ValidateRule image(mixed $rule, string $msg = '') static 验证图像文件 + * @method ValidateRule method(mixed $rule, string $msg = '') static 验证请求类型 + * @method ValidateRule dateFormat(mixed $rule, string $msg = '') static 验证时间和日期是否符合指定格式 + * @method ValidateRule unique(mixed $rule, string $msg = '') static 验证是否唯一 + * @method ValidateRule behavior(mixed $rule, string $msg = '') static 使用行为类验证 + * @method ValidateRule filter(mixed $rule, string $msg = '') static 使用filter_var方式验证 + * @method ValidateRule requireIf(mixed $rule, string $msg = '') static 验证某个字段等于某个值的时候必须 + * @method ValidateRule requireCallback(mixed $rule, string $msg = '') static 通过回调方法验证某个字段是否必须 + * @method ValidateRule requireWith(mixed $rule, string $msg = '') static 验证某个字段有值的情况下必须 + * @method ValidateRule must(mixed $rule = null, string $msg = '') static 必须验证 + */ +class ValidateRule +{ + // 验证字段的名称 + protected $title; + + // 当前验证规则 + protected $rule = []; + + // 验证提示信息 + protected $message = []; + + /** + * 添加验证因子 + * @access protected + * @param string $name 验证名称 + * @param mixed $rule 验证规则 + * @param string $msg 提示信息 + * @return $this + */ + protected function addItem(string $name, $rule = null, string $msg = '') + { + if ($rule || 0 === $rule) { + $this->rule[$name] = $rule; + } else { + $this->rule[] = $name; + } + + $this->message[] = $msg; + + return $this; + } + + /** + * 获取验证规则 + * @access public + * @return array + */ + public function getRule(): array + { + return $this->rule; + } + + /** + * 获取验证字段名称 + * @access public + * @return string + */ + public function getTitle(): string + { + return $this->title ?: ''; + } + + /** + * 获取验证提示 + * @access public + * @return array + */ + public function getMsg(): array + { + return $this->message; + } + + /** + * 设置验证字段名称 + * @access public + * @return $this + */ + public function title(string $title) + { + $this->title = $title; + + return $this; + } + + public function __call($method, $args) + { + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); + + return call_user_func_array([$this, 'addItem'], $args); + } + + public static function __callStatic($method, $args) + { + $rule = new static(); + + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); + + return call_user_func_array([$rule, 'addItem'], $args); + } +} diff --git a/vendor/topthink/framework/src/think/view/driver/Php.php b/vendor/topthink/framework/src/think/view/driver/Php.php new file mode 100644 index 0000000..0713a56 --- /dev/null +++ b/vendor/topthink/framework/src/think/view/driver/Php.php @@ -0,0 +1,191 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\view\driver; + +use RuntimeException; +use think\App; +use think\contract\TemplateHandlerInterface; +use think\helper\Str; + +/** + * PHP原生模板驱动 + */ +class Php implements TemplateHandlerInterface +{ + protected $template; + protected $content; + protected $app; + + // 模板引擎参数 + protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 视图目录名 + 'view_dir_name' => 'view', + // 应用模板路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + ]; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config = array_merge($this->config, (array) $config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists(string $template): bool + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch(string $template, array $data = []): void + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new RuntimeException('template not exists:' . $template); + } + + $this->template = $template; + + extract($data, EXTR_OVERWRITE); + + include $this->template; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display(string $content, array $data = []): void + { + $this->content = $content; + + extract($data, EXTR_OVERWRITE); + eval('?>' . $this->content); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate(string $template): string + { + $request = $this->app->request; + + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨应用调用 + [$app, $template] = explode('@', $template); + } + + if ($this->config['view_path'] && !isset($app)) { + $path = $this->config['view_path']; + } else { + $appName = isset($app) ? $app : $this->app->http->getName(); + $view = $this->config['view_dir_name']; + + if (is_dir($this->app->getAppPath() . $view)) { + $path = isset($app) ? $this->app->getBasePath() . ($appName ? $appName . DIRECTORY_SEPARATOR : '') . $view . DIRECTORY_SEPARATOR : $this->app->getAppPath() . $view . DIRECTORY_SEPARATOR; + } else { + $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . ($appName ? $appName . DIRECTORY_SEPARATOR : ''); + } + } + + $depr = $this->config['view_depr']; + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = $request->controller(); + if (strpos($controller, '.')) { + $pos = strrpos($controller, '.'); + $controller = substr($controller, 0, $pos) . '.' . Str::snake(substr($controller, $pos + 1)); + } else { + $controller = Str::snake($controller); + } + + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + if (2 == $this->config['auto_rule']) { + $template = $request->action(true); + } elseif (3 == $this->config['auto_rule']) { + $template = $request->action(); + } else { + $template = Str::snake($request->action()); + } + + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置模板引擎 + * @access private + * @param array $config 参数 + * @return void + */ + public function config(array $config): void + { + $this->config = array_merge($this->config, $config); + } + + /** + * 获取模板引擎配置 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getConfig(string $name) + { + return $this->config[$name] ?? null; + } +} diff --git a/vendor/topthink/framework/src/tpl/think_exception.tpl b/vendor/topthink/framework/src/tpl/think_exception.tpl new file mode 100644 index 0000000..7766caf --- /dev/null +++ b/vendor/topthink/framework/src/tpl/think_exception.tpl @@ -0,0 +1,502 @@ +'.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); + } +} +if (!function_exists('echo_value')) { + function echo_value($val) + { + if (is_array($val) || is_object($val)) { + echo htmlentities(json_encode($val, JSON_PRETTY_PRINT)); + } elseif (is_bool($val)) { + echo $val ? 'true' : 'false'; + } elseif (is_scalar($val)) { + echo htmlentities($val); + } else { + echo 'Resource'; + } + } +} +?> + + + + + 系统发生错误 + + + + + + $trace) { ?> +
+
+
+
+

+
+

+
+
+ +
+
    $value) { ?>
  1. ">
+
+ +
+

Call Stack

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

+
+ + + +
+

Exception Datas

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

Environment Variables

+ $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
empty
+ +
+ + + + + + + + diff --git a/vendor/topthink/framework/tests/AppTest.php b/vendor/topthink/framework/tests/AppTest.php new file mode 100644 index 0000000..6b86015 --- /dev/null +++ b/vendor/topthink/framework/tests/AppTest.php @@ -0,0 +1,215 @@ + 'class', + ]; + + public function register() + { + + } + + public function boot() + { + + } +} + +/** + * @property array initializers + */ +class AppTest extends TestCase +{ + /** @var App */ + protected $app; + + protected function setUp() + { + $this->app = new App(); + } + + protected function tearDown(): void + { + m::close(); + } + + public function testService() + { + $this->app->register(stdClass::class); + + $this->assertInstanceOf(stdClass::class, $this->app->getService(stdClass::class)); + + $service = m::mock(SomeService::class); + + $service->shouldReceive('register')->once(); + + $this->app->register($service); + + $this->assertEquals($service, $this->app->getService(SomeService::class)); + + $service2 = m::mock(SomeService::class); + + $service2->shouldReceive('register')->once(); + + $this->app->register($service2); + + $this->assertEquals($service, $this->app->getService(SomeService::class)); + + $this->app->register($service2, true); + + $this->assertEquals($service2, $this->app->getService(SomeService::class)); + + $service->shouldReceive('boot')->once(); + $service2->shouldReceive('boot')->once(); + + $this->app->boot(); + } + + public function testDebug() + { + $this->app->debug(false); + + $this->assertFalse($this->app->isDebug()); + + $this->app->debug(true); + + $this->assertTrue($this->app->isDebug()); + } + + public function testNamespace() + { + $namespace = 'test'; + + $this->app->setNamespace($namespace); + + $this->assertEquals($namespace, $this->app->getNamespace()); + } + + public function testVersion() + { + $this->assertEquals(App::VERSION, $this->app->version()); + } + + public function testPath() + { + $rootPath = __DIR__ . DIRECTORY_SEPARATOR; + + $app = new App($rootPath); + + $this->assertEquals($rootPath, $app->getRootPath()); + + $this->assertEquals(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR, $app->getThinkPath()); + + $this->assertEquals($rootPath . 'app' . DIRECTORY_SEPARATOR, $app->getAppPath()); + + $appPath = $rootPath . 'app' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR; + $app->setAppPath($appPath); + $this->assertEquals($appPath, $app->getAppPath()); + + $this->assertEquals($rootPath . 'app' . DIRECTORY_SEPARATOR, $app->getBasePath()); + + $this->assertEquals($rootPath . 'config' . DIRECTORY_SEPARATOR, $app->getConfigPath()); + + $this->assertEquals($rootPath . 'runtime' . DIRECTORY_SEPARATOR, $app->getRuntimePath()); + + $runtimePath = $rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR; + $app->setRuntimePath($runtimePath); + $this->assertEquals($runtimePath, $app->getRuntimePath()); + } + + /** + * @param vfsStreamDirectory $root + * @param bool $debug + * @return App + */ + protected function prepareAppForInitialize(vfsStreamDirectory $root, $debug = true) + { + $rootPath = $root->url() . DIRECTORY_SEPARATOR; + + $app = new App($rootPath); + + $initializer = m::mock(); + $initializer->shouldReceive('init')->once()->with($app); + + $app->instance($initializer->mockery_getName(), $initializer); + + (function () use ($initializer) { + $this->initializers = [$initializer->mockery_getName()]; + })->call($app); + + $env = m::mock(Env::class); + $env->shouldReceive('load')->once()->with($rootPath . '.env'); + $env->shouldReceive('get')->once()->with('config_ext', '.php')->andReturn('.php'); + $env->shouldReceive('get')->once()->with('app_debug')->andReturn($debug); + + $event = m::mock(Event::class); + $event->shouldReceive('trigger')->once()->with(AppInit::class); + $event->shouldReceive('bind')->once()->with([]); + $event->shouldReceive('listenEvents')->once()->with([]); + $event->shouldReceive('subscribe')->once()->with([]); + + $app->instance('env', $env); + $app->instance('event', $event); + + return $app; + } + + public function testInitialize() + { + $root = vfsStream::setup('rootDir', null, [ + '.env' => '', + 'app' => [ + 'common.php' => '', + 'event.php' => '[],"listen"=>[],"subscribe"=>[]];', + 'provider.php' => ' [ + 'app.php' => 'prepareAppForInitialize($root, true); + + $app->debug(false); + + $app->initialize(); + + $this->assertIsInt($app->getBeginMem()); + $this->assertIsFloat($app->getBeginTime()); + + $this->assertTrue($app->initialized()); + } + + public function testFactory() + { + $this->assertInstanceOf(stdClass::class, App::factory(stdClass::class)); + + $this->expectException(ClassNotFoundException::class); + + App::factory('SomeClass'); + } + + public function testParseClass() + { + $this->assertEquals('app\\controller\\SomeClass', $this->app->parseClass('controller', 'some_class')); + $this->app->setNamespace('app2'); + $this->assertEquals('app2\\controller\\SomeClass', $this->app->parseClass('controller', 'some_class')); + } + +} diff --git a/vendor/topthink/framework/tests/CacheTest.php b/vendor/topthink/framework/tests/CacheTest.php new file mode 100644 index 0000000..58947d3 --- /dev/null +++ b/vendor/topthink/framework/tests/CacheTest.php @@ -0,0 +1,149 @@ +app = m::mock(App::class)->makePartial(); + + Container::setInstance($this->app); + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + + $this->cache = new Cache($this->app); + } + + public function testGetConfig() + { + $config = [ + 'default' => 'file', + ]; + + $this->config->shouldReceive('get')->with('cache')->andReturn($config); + + $this->assertEquals($config, $this->cache->getConfig()); + + $this->expectException(InvalidArgumentException::class); + $this->cache->getStoreConfig('foo'); + } + + public function testCacheManagerInstances() + { + $this->config->shouldReceive('get')->with("cache.stores.single", null)->andReturn(['type' => 'file']); + + $channel1 = $this->cache->store('single'); + $channel2 = $this->cache->store('single'); + + $this->assertSame($channel1, $channel2); + } + + public function testFileCache() + { + $root = vfsStream::setup(); + + $this->config->shouldReceive('get')->with("cache.default", null)->andReturn('file'); + + $this->config->shouldReceive('get')->with("cache.stores.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]); + + $this->cache->set('foo', 5); + $this->cache->inc('foo'); + $this->assertEquals(6, $this->cache->get('foo')); + $this->cache->dec('foo', 2); + $this->assertEquals(4, $this->cache->get('foo')); + + $this->cache->set('bar', true); + $this->assertTrue($this->cache->get('bar')); + + $this->cache->set('baz', null); + $this->assertNull($this->cache->get('baz')); + + $this->assertTrue($this->cache->has('baz')); + $this->cache->delete('baz'); + $this->assertFalse($this->cache->has('baz')); + $this->assertNull($this->cache->get('baz')); + $this->assertFalse($this->cache->get('baz', false)); + + $this->assertTrue($root->hasChildren()); + $this->cache->clear(); + $this->assertFalse($root->hasChildren()); + + //tags + $this->cache->tag('foo')->set('bar', 'foobar'); + $this->assertEquals('foobar', $this->cache->get('bar')); + $this->cache->tag('foo')->clear(); + $this->assertFalse($this->cache->has('bar')); + + //multiple + $this->cache->setMultiple(['foo' => ['foobar', 'bar'], 'foobar' => ['foo', 'bar']]); + $this->assertEquals(['foo' => ['foobar', 'bar'], 'foobar' => ['foo', 'bar']], $this->cache->getMultiple(['foo', 'foobar'])); + $this->assertTrue($this->cache->deleteMultiple(['foo', 'foobar'])); + } + + public function testRedisCache() + { + if (extension_loaded('redis')) { + return; + } + $this->config->shouldReceive('get')->with("cache.default", null)->andReturn('redis'); + $this->config->shouldReceive('get')->with("cache.stores.redis", null)->andReturn(['type' => 'redis']); + + $redis = m::mock('overload:\Predis\Client'); + + $redis->shouldReceive("set")->once()->with('foo', 5)->andReturnTrue(); + $redis->shouldReceive("incrby")->once()->with('foo', 1)->andReturnTrue(); + $redis->shouldReceive("decrby")->once()->with('foo', 2)->andReturnTrue(); + $redis->shouldReceive("get")->once()->with('foo')->andReturn(6); + $redis->shouldReceive("get")->once()->with('foo')->andReturn(4); + $redis->shouldReceive("set")->once()->with('bar', serialize(true))->andReturnTrue(); + $redis->shouldReceive("set")->once()->with('baz', serialize(null))->andReturnTrue(); + $redis->shouldReceive("del")->once()->with('baz')->andReturnTrue(); + $redis->shouldReceive("flushDB")->once()->andReturnTrue(); + $redis->shouldReceive("set")->once()->with('bar', serialize('foobar'))->andReturnTrue(); + $redis->shouldReceive("sAdd")->once()->with('tag:' . md5('foo'), 'bar')->andReturnTrue(); + $redis->shouldReceive("sMembers")->once()->with('tag:' . md5('foo'))->andReturn(['bar']); + $redis->shouldReceive("del")->once()->with(['bar'])->andReturnTrue(); + $redis->shouldReceive("del")->once()->with('tag:' . md5('foo'))->andReturnTrue(); + + $this->cache->set('foo', 5); + $this->cache->inc('foo'); + $this->assertEquals(6, $this->cache->get('foo')); + $this->cache->dec('foo', 2); + $this->assertEquals(4, $this->cache->get('foo')); + + $this->cache->set('bar', true); + $this->cache->set('baz', null); + $this->cache->delete('baz'); + $this->cache->clear(); + + //tags + $this->cache->tag('foo')->set('bar', 'foobar'); + $this->cache->tag('foo')->clear(); + } +} diff --git a/vendor/topthink/framework/tests/ConfigTest.php b/vendor/topthink/framework/tests/ConfigTest.php new file mode 100644 index 0000000..271a34f --- /dev/null +++ b/vendor/topthink/framework/tests/ConfigTest.php @@ -0,0 +1,46 @@ +setContent(" 'value1','key2'=>'value2'];"); + $root->addChild($file); + + $config = new Config(); + + $config->load($file->url(), 'test'); + + $this->assertEquals('value1', $config->get('test.key1')); + $this->assertEquals('value2', $config->get('test.key2')); + + $this->assertSame(['key1' => 'value1', 'key2' => 'value2'], $config->get('test')); + } + + public function testSetAndGet() + { + $config = new Config(); + + $config->set([ + 'key1' => 'value1', + 'key2' => [ + 'key3' => 'value3', + ], + ], 'test'); + + $this->assertTrue($config->has('test.key1')); + $this->assertEquals('value1', $config->get('test.key1')); + $this->assertEquals('value3', $config->get('test.key2.key3')); + + $this->assertEquals(['key3' => 'value3'], $config->get('test.key2')); + $this->assertFalse($config->has('test.key3')); + $this->assertEquals('none', $config->get('test.key3', 'none')); + } +} diff --git a/vendor/topthink/framework/tests/ContainerTest.php b/vendor/topthink/framework/tests/ContainerTest.php new file mode 100644 index 0000000..72ff1d0 --- /dev/null +++ b/vendor/topthink/framework/tests/ContainerTest.php @@ -0,0 +1,313 @@ +name = $name; + } + + public function some(Container $container) + { + } + + protected function protectionFun() + { + return true; + } + + public static function test(Container $container) + { + return $container; + } + + public static function __make() + { + return new self('Taylor'); + } +} + +class SomeClass +{ + public $container; + + public $count = 0; + + public function __construct(Container $container) + { + $this->container = $container; + } +} + +class ContainerTest extends TestCase +{ + protected function tearDown(): void + { + Container::setInstance(null); + } + + public function testClosureResolution() + { + $container = new Container; + + Container::setInstance($container); + + $container->bind('name', function () { + return 'Taylor'; + }); + + $this->assertEquals('Taylor', $container->make('name')); + + $this->assertEquals('Taylor', Container::pull('name')); + } + + public function testGet() + { + $container = new Container; + + $this->expectException(ClassNotFoundException::class); + $this->expectExceptionMessage('class not exists: name'); + $container->get('name'); + + $container->bind('name', function () { + return 'Taylor'; + }); + + $this->assertSame('Taylor', $container->get('name')); + } + + public function testExist() + { + $container = new Container; + + $container->bind('name', function () { + return 'Taylor'; + }); + + $this->assertFalse($container->exists("name")); + + $container->make('name'); + + $this->assertTrue($container->exists('name')); + } + + public function testInstance() + { + $container = new Container; + + $container->bind('name', function () { + return 'Taylor'; + }); + + $this->assertEquals('Taylor', $container->get('name')); + + $container->bind('name2', Taylor::class); + + $object = new stdClass(); + + $this->assertFalse($container->exists('name2')); + + $container->instance('name2', $object); + + $this->assertTrue($container->exists('name2')); + + $this->assertTrue($container->exists(Taylor::class)); + + $this->assertEquals($object, $container->make(Taylor::class)); + + unset($container->name1); + + $this->assertFalse($container->exists('name1')); + + $container->delete('name2'); + + $this->assertFalse($container->exists('name2')); + + foreach ($container as $class => $instance) { + + } + } + + public function testBind() + { + $container = new Container; + + $object = new stdClass(); + + $container->bind(['name' => Taylor::class]); + + $container->bind('name2', $object); + + $container->bind('name3', Taylor::class); + + $container->name4 = $object; + + $container['name5'] = $object; + + $this->assertTrue(isset($container->name4)); + + $this->assertTrue(isset($container['name5'])); + + $this->assertInstanceOf(Taylor::class, $container->get('name')); + + $this->assertSame($object, $container->get('name2')); + + $this->assertSame($object, $container->name4); + + $this->assertSame($object, $container['name5']); + + $this->assertInstanceOf(Taylor::class, $container->get('name3')); + + unset($container['name']); + + $this->assertFalse(isset($container['name'])); + + unset($container->name3); + + $this->assertFalse(isset($container->name3)); + } + + public function testAutoConcreteResolution() + { + $container = new Container; + + $taylor = $container->make(Taylor::class); + + $this->assertInstanceOf(Taylor::class, $taylor); + $this->assertAttributeSame('Taylor', 'name', $taylor); + } + + public function testGetAndSetInstance() + { + $this->assertInstanceOf(Container::class, Container::getInstance()); + + $object = new stdClass(); + + Container::setInstance($object); + + $this->assertSame($object, Container::getInstance()); + + Container::setInstance(function () { + return $this; + }); + + $this->assertSame($this, Container::getInstance()); + } + + public function testResolving() + { + $container = new Container(); + $container->bind(Container::class, $container); + + $container->resolving(function (SomeClass $taylor, Container $container) { + $taylor->count++; + }); + $container->resolving(SomeClass::class, function (SomeClass $taylor, Container $container) { + $taylor->count++; + }); + + /** @var SomeClass $someClass */ + $someClass = $container->invokeClass(SomeClass::class); + $this->assertEquals(2, $someClass->count); + } + + public function testInvokeFunctionWithoutMethodThrowsException() + { + $this->expectException(FuncNotFoundException::class); + $this->expectExceptionMessage('function not exists: ContainerTestCallStub()'); + $container = new Container(); + $container->invokeFunction('ContainerTestCallStub', []); + } + + public function testInvokeProtectionMethod() + { + $container = new Container(); + $this->assertTrue($container->invokeMethod([Taylor::class, 'protectionFun'], [], true)); + } + + public function testInvoke() + { + $container = new Container(); + + Container::setInstance($container); + + $container->bind(Container::class, $container); + + $stub = $this->createMock(Taylor::class); + + $stub->expects($this->once())->method('some')->with($container)->will($this->returnSelf()); + + $container->invokeMethod([$stub, 'some']); + + $this->assertEquals('48', $container->invoke('ord', ['0'])); + + $this->assertSame($container, $container->invoke(Taylor::class . '::test', [])); + + $this->assertSame($container, $container->invokeMethod(Taylor::class . '::test')); + + $reflect = new ReflectionMethod($container, 'exists'); + + $this->assertTrue($container->invokeReflectMethod($container, $reflect, [Container::class])); + + $this->assertSame($container, $container->invoke(function (Container $container) { + return $container; + })); + + $this->assertSame($container, $container->invoke(Taylor::class . '::test')); + + $object = $container->invokeClass(SomeClass::class); + $this->assertInstanceOf(SomeClass::class, $object); + $this->assertSame($container, $object->container); + + $stdClass = new stdClass(); + + $container->invoke(function (Container $container, stdClass $stdObject, $key1, $lowKey, $key2 = 'default') use ($stdClass) { + $this->assertEquals('value1', $key1); + $this->assertEquals('default', $key2); + $this->assertEquals('value2', $lowKey); + $this->assertSame($stdClass, $stdObject); + return $container; + }, ['some' => $stdClass, 'key1' => 'value1', 'low_key' => 'value2']); + } + + public function testInvokeMethodNotExists() + { + $container = $this->resolveContainer(); + $this->expectException(FuncNotFoundException::class); + + $container->invokeMethod([SomeClass::class, 'any']); + } + + public function testInvokeClassNotExists() + { + $container = new Container(); + + Container::setInstance($container); + + $container->bind(Container::class, $container); + + $this->expectExceptionObject(new ClassNotFoundException('class not exists: SomeClass')); + + $container->invokeClass('SomeClass'); + } + + protected function resolveContainer() + { + $container = new Container(); + + Container::setInstance($container); + return $container; + } + +} diff --git a/vendor/topthink/framework/tests/DbTest.php b/vendor/topthink/framework/tests/DbTest.php new file mode 100644 index 0000000..cffdc84 --- /dev/null +++ b/vendor/topthink/framework/tests/DbTest.php @@ -0,0 +1,44 @@ +shouldReceive('get')->with('database.foo', null)->andReturn('foo'); + $this->assertEquals('foo', $db->getConfig('foo')); + + $config->shouldReceive('get')->with('database', [])->andReturn([]); + $this->assertEquals([], $db->getConfig()); + + $callback = function () { + }; + $event->shouldReceive('listen')->with('db.some', $callback); + $db->event('some', $callback); + + $event->shouldReceive('trigger')->with('db.some', null, false); + $db->trigger('some'); + } + +} diff --git a/vendor/topthink/framework/tests/EnvTest.php b/vendor/topthink/framework/tests/EnvTest.php new file mode 100644 index 0000000..cf2e65f --- /dev/null +++ b/vendor/topthink/framework/tests/EnvTest.php @@ -0,0 +1,82 @@ +setContent("key1=value1\nkey2=value2"); + $root->addChild($envFile); + + $env = new Env(); + + $env->load($envFile->url()); + + $this->assertEquals('value1', $env->get('key1')); + $this->assertEquals('value2', $env->get('key2')); + + $this->assertSame(['KEY1' => 'value1', 'KEY2' => 'value2'], $env->get()); + } + + public function testServerEnv() + { + $env = new Env(); + + $this->assertEquals('value2', $env->get('key2', 'value2')); + + putenv('PHP_KEY7=value7'); + putenv('PHP_KEY8=false'); + putenv('PHP_KEY9=true'); + + $this->assertEquals('value7', $env->get('key7')); + $this->assertFalse($env->get('KEY8')); + $this->assertTrue($env->get('key9')); + } + + public function testSetEnv() + { + $env = new Env(); + + $env->set([ + 'key1' => 'value1', + 'key2' => [ + 'key1' => 'value1-2', + ], + ]); + + $env->set('key3', 'value3'); + + $env->key4 = 'value4'; + + $env['key5'] = 'value5'; + + $this->assertEquals('value1', $env->get('key1')); + $this->assertEquals('value1-2', $env->get('key2.key1')); + + $this->assertEquals('value3', $env->get('key3')); + + $this->assertEquals('value4', $env->key4); + + $this->assertEquals('value5', $env['key5']); + + $this->expectException(Exception::class); + + unset($env['key5']); + } + + public function testHasEnv() + { + $env = new Env(); + $env->set(['foo' => 'bar']); + $this->assertTrue($env->has('foo')); + $this->assertTrue(isset($env->foo)); + $this->assertTrue($env->offsetExists('foo')); + } +} diff --git a/vendor/topthink/framework/tests/EventTest.php b/vendor/topthink/framework/tests/EventTest.php new file mode 100644 index 0000000..707a35b --- /dev/null +++ b/vendor/topthink/framework/tests/EventTest.php @@ -0,0 +1,143 @@ +app = m::mock(App::class)->makePartial(); + + Container::setInstance($this->app); + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + + $this->event = new Event($this->app); + } + + public function testBasic() + { + $this->event->bind(['foo' => 'baz']); + + $this->event->listen('foo', function ($bar) { + $this->assertEquals('bar', $bar); + }); + + $this->assertTrue($this->event->hasListener('foo')); + + $this->event->trigger('baz', 'bar'); + + $this->event->remove('foo'); + + $this->assertFalse($this->event->hasListener('foo')); + } + + public function testOnceEvent() + { + $this->event->listen('AppInit', function ($bar) { + $this->assertEquals('bar', $bar); + return 'foo'; + }); + + $this->assertEquals('foo', $this->event->trigger('AppInit', 'bar', true)); + $this->assertEquals(['foo'], $this->event->trigger('AppInit', 'bar')); + } + + public function testClassListener() + { + $listener = m::mock("overload:SomeListener", TestListener::class); + + $listener->shouldReceive('handle')->andReturnTrue(); + + $this->event->listen('some', "SomeListener"); + + $this->assertTrue($this->event->until('some')); + } + + public function testSubscribe() + { + $listener = m::mock("overload:SomeListener", TestListener::class); + + $listener->shouldReceive('subscribe')->andReturnUsing(function (Event $event) use ($listener) { + + $listener->shouldReceive('onBar')->once()->andReturnFalse(); + + $event->listenEvents(['SomeListener::onBar' => [[$listener, 'onBar']]]); + }); + + $this->event->subscribe('SomeListener'); + + $this->assertTrue($this->event->hasListener('SomeListener::onBar')); + + $this->event->trigger('SomeListener::onBar'); + } + + public function testAutoObserve() + { + $listener = m::mock("overload:SomeListener", TestListener::class); + + $listener->shouldReceive('onBar')->once(); + + $this->app->shouldReceive('make')->with('SomeListener')->andReturn($listener); + + $this->event->observe('SomeListener'); + + $this->event->trigger('bar'); + } + + public function testWithoutEvent() + { + $this->event->withEvent(false); + + $this->event->listen('SomeListener', TestListener::class); + + $this->assertFalse($this->event->hasListener('SomeListener')); + } + +} + +class TestListener +{ + public function handle() + { + + } + + public function onBar() + { + + } + + public function onFoo() + { + + } + + public function subscribe() + { + + } +} diff --git a/vendor/topthink/framework/tests/FilesystemTest.php b/vendor/topthink/framework/tests/FilesystemTest.php new file mode 100644 index 0000000..df5ffe2 --- /dev/null +++ b/vendor/topthink/framework/tests/FilesystemTest.php @@ -0,0 +1,131 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class); + $this->config->shouldReceive('get')->with('filesystem.default', null)->andReturn('local'); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + $this->filesystem = new Filesystem($this->app); + + $this->root = vfsStream::setup('rootDir'); + } + + protected function tearDown(): void + { + m::close(); + } + + public function testDisk() + { + $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([ + 'type' => 'local', + 'root' => $this->root->url(), + ]); + + $this->config->shouldReceive('get')->with('filesystem.disks.foo', null)->andReturn([ + 'type' => 'local', + 'root' => $this->root->url(), + ]); + + $this->assertInstanceOf(Local::class, $this->filesystem->disk()); + + $this->assertInstanceOf(Local::class, $this->filesystem->disk('foo')); + } + + public function testCache() + { + $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([ + 'type' => 'local', + 'root' => $this->root->url(), + 'cache' => true, + ]); + + $this->assertInstanceOf(Local::class, $this->filesystem->disk()); + + $this->config->shouldReceive('get')->with('filesystem.disks.cache', null)->andReturn([ + 'type' => NullDriver::class, + 'root' => $this->root->url(), + 'cache' => [ + 'store' => 'flysystem', + ], + ]); + + $cache = m::mock(Cache::class); + + $cacheDriver = m::mock(File::class); + + $cache->shouldReceive('store')->once()->with('flysystem')->andReturn($cacheDriver); + + $this->app->shouldReceive('make')->with(Cache::class)->andReturn($cache); + + $cacheDriver->shouldReceive('get')->with('flysystem')->once()->andReturn(null); + + $cacheDriver->shouldReceive('set')->withAnyArgs(); + + $this->filesystem->disk('cache')->put('test.txt', 'aa'); + } + + public function testPutFile() + { + $root = vfsStream::setup('rootDir', null, [ + 'foo.jpg' => 'hello', + ]); + + $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([ + 'type' => NullDriver::class, + 'root' => $root->url(), + 'cache' => true, + ]); + + $file = m::mock(\think\File::class); + + $file->shouldReceive('hashName')->with(null)->once()->andReturn('foo.jpg'); + + $file->shouldReceive('getRealPath')->once()->andReturn($root->getChild('foo.jpg')->url()); + + $this->filesystem->putFile('test', $file); + } +} + +class NullDriver extends Driver +{ + protected function createAdapter(): AdapterInterface + { + return new NullAdapter(); + } +} diff --git a/vendor/topthink/framework/tests/HttpTest.php b/vendor/topthink/framework/tests/HttpTest.php new file mode 100644 index 0000000..8b6f28f --- /dev/null +++ b/vendor/topthink/framework/tests/HttpTest.php @@ -0,0 +1,154 @@ +app = m::mock(App::class)->makePartial(); + + $this->http = m::mock(Http::class, [$this->app])->shouldAllowMockingProtectedMethods()->makePartial(); + } + + protected function prepareApp($request, $response) + { + $this->app->shouldReceive('instance')->once()->with('request', $request); + $this->app->shouldReceive('initialized')->once()->andReturnFalse(); + $this->app->shouldReceive('initialize')->once(); + $this->app->shouldReceive('get')->with('request')->andReturn($request); + + $route = m::mock(Route::class); + + $route->shouldReceive('dispatch')->withArgs(function ($req, $withRoute) use ($request) { + if ($withRoute) { + $withRoute(); + } + return $req === $request; + })->andReturn($response); + + $route->shouldReceive('config')->with('route_annotation')->andReturn(true); + + $this->app->shouldReceive('get')->with('route')->andReturn($route); + + $console = m::mock(Console::class); + + $console->shouldReceive('call'); + + $this->app->shouldReceive('get')->with('console')->andReturn($console); + } + + public function testRun() + { + $root = vfsStream::setup('rootDir', null, [ + 'app' => [ + 'controller' => [], + 'middleware.php' => ' [ + 'route.php' => 'app->shouldReceive('getBasePath')->andReturn($root->getChild('app')->url() . DIRECTORY_SEPARATOR); + $this->app->shouldReceive('getRootPath')->andReturn($root->url() . DIRECTORY_SEPARATOR); + + $request = m::mock(Request::class)->makePartial(); + $response = m::mock(Response::class)->makePartial(); + + $this->prepareApp($request, $response); + + $this->assertEquals($response, $this->http->run($request)); + } + + public function multiAppRunProvider() + { + $request1 = m::mock(Request::class)->makePartial(); + $request1->shouldReceive('subDomain')->andReturn('www'); + $request1->shouldReceive('host')->andReturn('www.domain.com'); + + $request2 = m::mock(Request::class)->makePartial(); + $request2->shouldReceive('subDomain')->andReturn('app2'); + $request2->shouldReceive('host')->andReturn('app2.domain.com'); + + $request3 = m::mock(Request::class)->makePartial(); + $request3->shouldReceive('pathinfo')->andReturn('some1/a/b/c'); + + $request4 = m::mock(Request::class)->makePartial(); + $request4->shouldReceive('pathinfo')->andReturn('app3/a/b/c'); + + $request5 = m::mock(Request::class)->makePartial(); + $request5->shouldReceive('pathinfo')->andReturn('some2/a/b/c'); + + return [ + [$request1, true, 'app1'], + [$request2, true, 'app2'], + [$request3, true, 'app3'], + [$request4, true, null], + [$request5, true, 'some2', 'path'], + [$request1, false, 'some3'], + ]; + } + + public function testRunWithException() + { + $request = m::mock(Request::class); + $response = m::mock(Response::class); + + $this->app->shouldReceive('instance')->once()->with('request', $request); + + $exception = new Exception(); + + $this->http->shouldReceive('runWithRequest')->once()->with($request)->andThrow($exception); + + $handle = m::mock(Handle::class); + + $handle->shouldReceive('report')->once()->with($exception); + $handle->shouldReceive('render')->once()->with($request, $exception)->andReturn($response); + + $this->app->shouldReceive('make')->with(Handle::class)->andReturn($handle); + + $this->assertEquals($response, $this->http->run($request)); + } + + public function testEnd() + { + $response = m::mock(Response::class); + $event = m::mock(Event::class); + $event->shouldReceive('trigger')->once()->with(HttpEnd::class, $response); + $this->app->shouldReceive('get')->once()->with('event')->andReturn($event); + $log = m::mock(Log::class); + $log->shouldReceive('save')->once(); + $this->app->shouldReceive('get')->once()->with('log')->andReturn($log); + + $this->http->end($response); + } + +} diff --git a/vendor/topthink/framework/tests/LogTest.php b/vendor/topthink/framework/tests/LogTest.php new file mode 100644 index 0000000..269306f --- /dev/null +++ b/vendor/topthink/framework/tests/LogTest.php @@ -0,0 +1,143 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + $this->app->shouldReceive('runningInConsole')->andReturn(false); + + $this->log = new Log($this->app); + } + + public function testGetConfig() + { + $config = [ + 'default' => 'file', + ]; + + $this->config->shouldReceive('get')->with('log')->andReturn($config); + + $this->assertEquals($config, $this->log->getConfig()); + + $this->expectException(InvalidArgumentException::class); + $this->log->getChannelConfig('foo'); + } + + public function testChannel() + { + $this->assertInstanceOf(ChannelSet::class, $this->log->channel(['file', 'mail'])); + } + + public function testLogManagerInstances() + { + $this->config->shouldReceive('get')->with("log.channels.single", null)->andReturn(['type' => 'file']); + + $channel1 = $this->log->channel('single'); + $channel2 = $this->log->channel('single'); + + $this->assertSame($channel1, $channel2); + } + + public function testFileLog() + { + $root = vfsStream::setup(); + + $this->config->shouldReceive('get')->with("log.default", null)->andReturn('file'); + + $this->config->shouldReceive('get')->with("log.channels.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]); + + $this->log->info('foo'); + + $this->assertEquals($this->log->getLog(), ['info' => ['foo']]); + + $this->log->clear(); + + $this->assertEmpty($this->log->getLog()); + + $this->log->error('foo'); + $this->assertArrayHasKey('error', $this->log->getLog()); + + $this->log->emergency('foo'); + $this->assertArrayHasKey('emergency', $this->log->getLog()); + + $this->log->alert('foo'); + $this->assertArrayHasKey('alert', $this->log->getLog()); + + $this->log->critical('foo'); + $this->assertArrayHasKey('critical', $this->log->getLog()); + + $this->log->warning('foo'); + $this->assertArrayHasKey('warning', $this->log->getLog()); + + $this->log->notice('foo'); + $this->assertArrayHasKey('notice', $this->log->getLog()); + + $this->log->debug('foo'); + $this->assertArrayHasKey('debug', $this->log->getLog()); + + $this->log->sql('foo'); + $this->assertArrayHasKey('sql', $this->log->getLog()); + + $this->log->custom('foo'); + $this->assertArrayHasKey('custom', $this->log->getLog()); + + $this->log->write('foo'); + $this->assertTrue($root->hasChildren()); + $this->assertEmpty($this->log->getLog()); + + $this->log->close(); + + $this->log->info('foo'); + + $this->assertEmpty($this->log->getLog()); + } + + public function testSave() + { + $root = vfsStream::setup(); + + $this->config->shouldReceive('get')->with("log.default", null)->andReturn('file'); + + $this->config->shouldReceive('get')->with("log.channels.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]); + + $this->log->info('foo'); + + $this->log->save(); + + $this->assertTrue($root->hasChildren()); + } + +} diff --git a/vendor/topthink/framework/tests/MiddlewareTest.php b/vendor/topthink/framework/tests/MiddlewareTest.php new file mode 100644 index 0000000..bbd092d --- /dev/null +++ b/vendor/topthink/framework/tests/MiddlewareTest.php @@ -0,0 +1,121 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + $this->app->shouldReceive('runningInConsole')->andReturn(false); + + $this->middleware = new Middleware($this->app); + } + + public function testSetMiddleware() + { + $this->middleware->add('BarMiddleware', 'bar'); + + $this->assertEquals(1, count($this->middleware->all('bar'))); + + $this->middleware->controller('BarMiddleware'); + $this->assertEquals(1, count($this->middleware->all('controller'))); + + $this->middleware->import(['FooMiddleware']); + $this->assertEquals(1, count($this->middleware->all())); + + $this->middleware->unshift(['BazMiddleware', 'baz']); + $this->assertEquals(2, count($this->middleware->all())); + $this->assertEquals([['BazMiddleware', 'handle'], 'baz'], $this->middleware->all()[0]); + + $this->config->shouldReceive('get')->with('middleware.alias', [])->andReturn(['foo' => ['FooMiddleware', 'FarMiddleware']]); + + $this->middleware->add('foo'); + $this->assertEquals(3, count($this->middleware->all())); + $this->middleware->add(function () { + }); + $this->middleware->add(function () { + }); + $this->assertEquals(5, count($this->middleware->all())); + } + + public function testPipelineAndEnd() + { + $bar = m::mock("overload:BarMiddleware"); + $foo = m::mock("overload:FooMiddleware", Foo::class); + + $request = m::mock(Request::class); + $response = m::mock(Response::class); + + $e = new Exception(); + + $handle = m::mock(Handle::class); + $handle->shouldReceive('report')->with($e)->andReturnNull(); + $handle->shouldReceive('render')->with($request, $e)->andReturn($response); + + $foo->shouldReceive('handle')->once()->andReturnUsing(function ($request, $next) { + return $next($request); + }); + $bar->shouldReceive('handle')->once()->andReturnUsing(function ($request, $next) use ($e) { + $next($request); + throw $e; + }); + + $foo->shouldReceive('end')->once()->with($response)->andReturnNull(); + + $this->app->shouldReceive('make')->with(Handle::class)->andReturn($handle); + + $this->config->shouldReceive('get')->once()->with('middleware.priority', [])->andReturn(['FooMiddleware', 'BarMiddleware']); + + $this->middleware->import([function ($request, $next) { + return $next($request); + }, 'BarMiddleware', 'FooMiddleware']); + + $this->assertInstanceOf(Pipeline::class, $pipeline = $this->middleware->pipeline()); + + $pipeline->send($request)->then(function ($request) use ($e, $response) { + throw $e; + }); + + $this->middleware->end($response); + } +} + +class Foo +{ + public function end(Response $response) + { + } +} diff --git a/vendor/topthink/framework/tests/SessionTest.php b/vendor/topthink/framework/tests/SessionTest.php new file mode 100644 index 0000000..b3b48a7 --- /dev/null +++ b/vendor/topthink/framework/tests/SessionTest.php @@ -0,0 +1,225 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + $handlerClass = "\\think\\session\\driver\\Test" . Str::random(10); + $this->config->shouldReceive("get")->with("session.type", "file")->andReturn($handlerClass); + $this->session = new Session($this->app); + + $this->handler = m::mock('overload:' . $handlerClass, SessionHandlerInterface::class); + } + + public function testLoadData() + { + $data = [ + "bar" => 'foo', + ]; + + $id = md5(uniqid()); + + $this->handler->shouldReceive("read")->once()->with($id)->andReturn(serialize($data)); + + $this->session->setId($id); + $this->session->init(); + + $this->assertEquals('foo', $this->session->get('bar')); + $this->assertTrue($this->session->has('bar')); + $this->assertFalse($this->session->has('foo')); + + $this->session->set('foo', 'bar'); + $this->assertTrue($this->session->has('foo')); + + $this->assertEquals('bar', $this->session->pull('foo')); + $this->assertFalse($this->session->has('foo')); + } + + public function testSave() + { + + $id = md5(uniqid()); + + $this->handler->shouldReceive('read')->once()->with($id)->andReturn(""); + + $this->handler->shouldReceive('write')->once()->with($id, serialize([ + "bar" => 'foo', + ]))->andReturnTrue(); + + $this->session->setId($id); + $this->session->init(); + + $this->session->set('bar', 'foo'); + + $this->session->save(); + } + + public function testFlash() + { + $this->session->flash('foo', 'bar'); + $this->session->flash('bar', 0); + $this->session->flash('baz', true); + + $this->assertTrue($this->session->has('foo')); + $this->assertEquals('bar', $this->session->get('foo')); + $this->assertEquals(0, $this->session->get('bar')); + $this->assertTrue($this->session->get('baz')); + + $this->session->clearFlashData(); + + $this->assertTrue($this->session->has('foo')); + $this->assertEquals('bar', $this->session->get('foo')); + $this->assertEquals(0, $this->session->get('bar')); + + $this->session->clearFlashData(); + + $this->assertFalse($this->session->has('foo')); + $this->assertNull($this->session->get('foo')); + + $this->session->flash('foo', 'bar'); + $this->assertTrue($this->session->has('foo')); + $this->session->clearFlashData(); + $this->session->reflash(); + $this->session->clearFlashData(); + + $this->assertTrue($this->session->has('foo')); + } + + public function testClear() + { + $this->session->set('bar', 'foo'); + $this->assertEquals('foo', $this->session->get('bar')); + $this->session->clear(); + $this->assertFalse($this->session->has('foo')); + } + + public function testSetName() + { + $this->session->setName('foo'); + $this->assertEquals('foo', $this->session->getName()); + } + + public function testDestroy() + { + $id = md5(uniqid()); + + $this->handler->shouldReceive('read')->once()->with($id)->andReturn(""); + $this->handler->shouldReceive('delete')->once()->with($id)->andReturnTrue(); + + $this->session->setId($id); + $this->session->init(); + + $this->session->set('bar', 'foo'); + + $this->session->destroy(); + + $this->assertFalse($this->session->has('bar')); + + $this->assertNotEquals($id, $this->session->getId()); + } + + public function testFileHandler() + { + $root = vfsStream::setup(); + + vfsStream::newFile('bar') + ->at($root) + ->lastModified(time()); + + vfsStream::newFile('bar') + ->at(vfsStream::newDirectory("foo")->at($root)) + ->lastModified(100); + + $this->assertTrue($root->hasChild("bar")); + $this->assertTrue($root->hasChild("foo/bar")); + + $handler = new TestFileHandle($this->app, [ + 'path' => $root->url(), + 'gc_probability' => 1, + 'gc_divisor' => 1, + ]); + + $this->assertTrue($root->hasChild("bar")); + $this->assertFalse($root->hasChild("foo/bar")); + + $id = md5(uniqid()); + $handler->write($id, "bar"); + + $this->assertTrue($root->hasChild("sess_{$id}")); + + $this->assertEquals("bar", $handler->read($id)); + + $handler->delete($id); + + $this->assertFalse($root->hasChild("sess_{$id}")); + } + + public function testCacheHandler() + { + $id = md5(uniqid()); + + $cache = m::mock(\think\Cache::class); + + $store = m::mock(Driver::class); + + $cache->shouldReceive('store')->once()->with('redis')->andReturn($store); + + $handler = new Cache($cache, ['store' => 'redis']); + + $store->shouldReceive("set")->with($id, "bar", 1440)->once()->andReturnTrue(); + $handler->write($id, "bar"); + + $store->shouldReceive("get")->with($id)->once()->andReturn("bar"); + $this->assertEquals("bar", $handler->read($id)); + + $store->shouldReceive("delete")->with($id)->once()->andReturnTrue(); + $handler->delete($id); + } +} + +class TestFileHandle extends File +{ + protected function writeFile($path, $content): bool + { + return (bool) file_put_contents($path, $content); + } +} diff --git a/vendor/topthink/framework/tests/ViewTest.php b/vendor/topthink/framework/tests/ViewTest.php new file mode 100644 index 0000000..e413510 --- /dev/null +++ b/vendor/topthink/framework/tests/ViewTest.php @@ -0,0 +1,127 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + + $this->view = new View($this->app); + } + + public function testAssignData() + { + $this->view->assign('foo', 'bar'); + $this->view->assign(['baz' => 'boom']); + $this->view->qux = "corge"; + + $this->assertEquals('bar', $this->view->foo); + $this->assertEquals('boom', $this->view->baz); + $this->assertEquals('corge', $this->view->qux); + $this->assertTrue(isset($this->view->qux)); + } + + public function testRender() + { + $this->config->shouldReceive("get")->with("view.type", 'php')->andReturn(TestTemplate::class); + + $this->view->filter(function ($content) { + return $content; + }); + + $this->assertEquals("fetch", $this->view->fetch('foo')); + $this->assertEquals("display", $this->view->display('foo')); + } + +} + +class TestTemplate implements TemplateHandlerInterface +{ + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists(string $template): bool + { + return true; + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch(string $template, array $data = []): void + { + echo "fetch"; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display(string $content, array $data = []): void + { + echo "display"; + } + + /** + * 配置模板引擎 + * @access private + * @param array $config 参数 + * @return void + */ + public function config(array $config): void + { + // TODO: Implement config() method. + } + + /** + * 获取模板引擎配置 + * @access public + * @param string $name 参数名 + * @return void + */ + public function getConfig(string $name) + { + // TODO: Implement getConfig() method. + } +} diff --git a/vendor/topthink/framework/tests/bootstrap.php b/vendor/topthink/framework/tests/bootstrap.php new file mode 100644 index 0000000..991ea43 --- /dev/null +++ b/vendor/topthink/framework/tests/bootstrap.php @@ -0,0 +1,3 @@ + 以下类库都在`\\think\\helper`命名空间下 + +## Str + +> 字符串操作 + +``` +// 检查字符串中是否包含某些字符串 +Str::contains($haystack, $needles) + +// 检查字符串是否以某些字符串结尾 +Str::endsWith($haystack, $needles) + +// 获取指定长度的随机字母数字组合的字符串 +Str::random($length = 16) + +// 字符串转小写 +Str::lower($value) + +// 字符串转大写 +Str::upper($value) + +// 获取字符串的长度 +Str::length($value) + +// 截取字符串 +Str::substr($string, $start, $length = null) + +``` \ No newline at end of file diff --git a/vendor/topthink/think-helper/composer.json b/vendor/topthink/think-helper/composer.json new file mode 100644 index 0000000..b68c43b --- /dev/null +++ b/vendor/topthink/think-helper/composer.json @@ -0,0 +1,22 @@ +{ + "name": "topthink/think-helper", + "description": "The ThinkPHP6 Helper Package", + "license": "Apache-2.0", + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "php": ">=7.1.0" + }, + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "src/helper.php" + ] + } +} diff --git a/vendor/topthink/think-helper/src/Collection.php b/vendor/topthink/think-helper/src/Collection.php new file mode 100644 index 0000000..f3d0a83 --- /dev/null +++ b/vendor/topthink/think-helper/src/Collection.php @@ -0,0 +1,651 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use think\contract\Arrayable; +use think\contract\Jsonable; +use think\helper\Arr; + +/** + * 数据集管理类 + */ +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Arrayable, Jsonable +{ + /** + * 数据集数据 + * @var array + */ + protected $items = []; + + public function __construct($items = []) + { + $this->items = $this->convertToArray($items); + } + + public static function make($items = []) + { + return new static($items); + } + + /** + * 是否为空 + * @access public + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->items); + } + + public function toArray(): array + { + return array_map(function ($value) { + return $value instanceof Arrayable ? $value->toArray() : $value; + }, $this->items); + } + + public function all(): array + { + return $this->items; + } + + /** + * 合并数组 + * + * @access public + * @param mixed $items 数据 + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->convertToArray($items))); + } + + /** + * 按指定键整理数据 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 键名 + * @return array + */ + public function dictionary($items = null, string &$indexKey = null) + { + if ($items instanceof self) { + $items = $items->all(); + } + + $items = is_null($items) ? $this->items : $items; + + if ($items && empty($indexKey)) { + $indexKey = is_array($items[0]) ? 'id' : $items[0]->getPk(); + } + + if (isset($indexKey) && is_string($indexKey)) { + return array_column($items, null, $indexKey); + } + + return $items; + } + + /** + * 比较数组,返回差集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function diff($items, string $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $diff = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (!isset($dictionary[$item[$indexKey]])) { + $diff[] = $item; + } + } + } + + return new static($diff); + } + + /** + * 比较数组,返回交集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function intersect($items, string $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $intersect = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (isset($dictionary[$item[$indexKey]])) { + $intersect[] = $item; + } + } + } + + return new static($intersect); + } + + /** + * 交换数组中的键和值 + * + * @access public + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * 返回数组中所有的键名 + * + * @access public + * @return static + */ + public function keys() + { + return new static(array_keys($this->items)); + } + + /** + * 返回数组中所有的值组成的新 Collection 实例 + * @access public + * @return static + */ + public function values() + { + return new static(array_values($this->items)); + } + + /** + * 删除数组的最后一个元素(出栈) + * + * @access public + * @return mixed + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * 通过使用用户自定义函数,以字符串返回数组 + * + * @access public + * @param callable $callback 调用方法 + * @param mixed $initial + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * 以相反的顺序返回数组。 + * + * @access public + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * 删除数组中首个元素,并返回被删除元素的值 + * + * @access public + * @return mixed + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * 在数组结尾插入一个元素 + * @access public + * @param mixed $value 元素 + * @param string $key KEY + * @return void + */ + public function push($value, string $key = null): void + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * 把一个数组分割为新的数组块. + * + * @access public + * @param int $size 块大小 + * @param bool $preserveKeys + * @return static + */ + public function chunk(int $size, bool $preserveKeys = false) + { + $chunks = []; + + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * 在数组开头插入一个元素 + * @access public + * @param mixed $value 元素 + * @param string $key KEY + * @return void + */ + public function unshift($value, string $key = null): void + { + if (is_null($key)) { + array_unshift($this->items, $value); + } else { + $this->items = [$key => $value] + $this->items; + } + } + + /** + * 给每个元素执行个回调 + * + * @access public + * @param callable $callback 回调 + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + + if (false === $result) { + break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * 用回调函数处理数组中的元素 + * @access public + * @param callable|null $callback 回调 + * @return static + */ + public function map(callable $callback) + { + return new static(array_map($callback, $this->items)); + } + + /** + * 用回调函数过滤数组中的元素 + * @access public + * @param callable|null $callback 回调 + * @return static + */ + public function filter(callable $callback = null) + { + if ($callback) { + return new static(array_filter($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + /** + * 根据字段条件过滤数组中的元素 + * @access public + * @param string $field 字段名 + * @param mixed $operator 操作符 + * @param mixed $value 数据 + * @return static + */ + public function where(string $field, $operator, $value = null) + { + if (is_null($value)) { + $value = $operator; + $operator = '='; + } + + return $this->filter(function ($data) use ($field, $operator, $value) { + if (strpos($field, '.')) { + list($field, $relation) = explode('.', $field); + + $result = $data[$field][$relation] ?? null; + } else { + $result = $data[$field] ?? null; + } + + switch (strtolower($operator)) { + case '===': + return $result === $value; + case '!==': + return $result !== $value; + case '!=': + case '<>': + return $result != $value; + case '>': + return $result > $value; + case '>=': + return $result >= $value; + case '<': + return $result < $value; + case '<=': + return $result <= $value; + case 'like': + return is_string($result) && false !== strpos($result, $value); + case 'not like': + return is_string($result) && false === strpos($result, $value); + case 'in': + return is_scalar($result) && in_array($result, $value, true); + case 'not in': + return is_scalar($result) && !in_array($result, $value, true); + case 'between': + list($min, $max) = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result >= $min && $result <= $max; + case 'not between': + list($min, $max) = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result > $max || $result < $min; + case '==': + case '=': + default: + return $result == $value; + } + }); + } + + /** + * LIKE过滤 + * @access public + * @param string $field 字段名 + * @param string $value 数据 + * @return static + */ + public function whereLike(string $field, string $value) + { + return $this->where($field, 'like', $value); + } + + /** + * NOT LIKE过滤 + * @access public + * @param string $field 字段名 + * @param string $value 数据 + * @return static + */ + public function whereNotLike(string $field, string $value) + { + return $this->where($field, 'not like', $value); + } + + /** + * IN过滤 + * @access public + * @param string $field 字段名 + * @param array $value 数据 + * @return static + */ + public function whereIn(string $field, array $value) + { + return $this->where($field, 'in', $value); + } + + /** + * NOT IN过滤 + * @access public + * @param string $field 字段名 + * @param array $value 数据 + * @return static + */ + public function whereNotIn(string $field, array $value) + { + return $this->where($field, 'not in', $value); + } + + /** + * BETWEEN 过滤 + * @access public + * @param string $field 字段名 + * @param mixed $value 数据 + * @return static + */ + public function whereBetween(string $field, $value) + { + return $this->where($field, 'between', $value); + } + + /** + * NOT BETWEEN 过滤 + * @access public + * @param string $field 字段名 + * @param mixed $value 数据 + * @return static + */ + public function whereNotBetween(string $field, $value) + { + return $this->where($field, 'not between', $value); + } + + /** + * 返回数据中指定的一列 + * @access public + * @param string $columnKey 键名 + * @param string $indexKey 作为索引值的列 + * @return array + */ + public function column(string $columnKey, string $indexKey = null) + { + return array_column($this->items, $columnKey, $indexKey); + } + + /** + * 对数组排序 + * + * @access public + * @param callable|null $callback 回调 + * @return static + */ + public function sort(callable $callback = null) + { + $items = $this->items; + + $callback = $callback ?: function ($a, $b) { + return $a == $b ? 0 : (($a < $b) ? -1 : 1); + }; + + uasort($items, $callback); + + return new static($items); + } + + /** + * 指定字段排序 + * @access public + * @param string $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order(string $field, string $order = null) + { + return $this->sort(function ($a, $b) use ($field, $order) { + $fieldA = $a[$field] ?? null; + $fieldB = $b[$field] ?? null; + + return 'desc' == strtolower($order) ? strcmp($fieldB, $fieldA) : strcmp($fieldA, $fieldB); + }); + } + + /** + * 将数组打乱 + * + * @access public + * @return static + */ + public function shuffle() + { + $items = $this->items; + + shuffle($items); + + return new static($items); + } + + /** + * 获取最后一个单元数据 + * + * @access public + * @param callable|null $callback + * @param null $default + * @return mixed + */ + public function first(callable $callback = null, $default = null) + { + return Arr::first($this->items, $callback, $default); + } + + /** + * 获取第一个单元数据 + * + * @access public + * @param callable|null $callback + * @param null $default + * @return mixed + */ + public function last(callable $callback = null, $default = null) + { + return Arr::last($this->items, $callback, $default); + } + + /** + * 截取数组 + * + * @access public + * @param int $offset 起始位置 + * @param int $length 截取长度 + * @param bool $preserveKeys preserveKeys + * @return static + */ + public function slice(int $offset, int $length = null, bool $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + // ArrayAccess + public function offsetExists($offset) + { + return array_key_exists($offset, $this->items); + } + + public function offsetGet($offset) + { + return $this->items[$offset]; + } + + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->items[] = $value; + } else { + $this->items[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + //Countable + public function count() + { + return count($this->items); + } + + //IteratorAggregate + public function getIterator() + { + return new ArrayIterator($this->items); + } + + //JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换当前数据集为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson(int $options = JSON_UNESCAPED_UNICODE): string + { + return json_encode($this->toArray(), $options); + } + + public function __toString() + { + return $this->toJson(); + } + + /** + * 转换成数组 + * + * @access public + * @param mixed $items 数据 + * @return array + */ + protected function convertToArray($items): array + { + if ($items instanceof self) { + return $items->all(); + } + + return (array) $items; + } +} diff --git a/vendor/topthink/think-helper/src/contract/Arrayable.php b/vendor/topthink/think-helper/src/contract/Arrayable.php new file mode 100644 index 0000000..7c6b992 --- /dev/null +++ b/vendor/topthink/think-helper/src/contract/Arrayable.php @@ -0,0 +1,8 @@ + +// +---------------------------------------------------------------------- + +use think\Collection; +use think\helper\Arr; + +if (!function_exists('throw_if')) { + /** + * 按条件抛异常 + * + * @param mixed $condition + * @param Throwable|string $exception + * @param array ...$parameters + * @return mixed + * + * @throws Throwable + */ + function throw_if($condition, $exception, ...$parameters) + { + if ($condition) { + throw (is_string($exception) ? new $exception(...$parameters) : $exception); + } + + return $condition; + } +} + +if (!function_exists('throw_unless')) { + /** + * 按条件抛异常 + * + * @param mixed $condition + * @param Throwable|string $exception + * @param array ...$parameters + * @return mixed + * @throws Throwable + */ + function throw_unless($condition, $exception, ...$parameters) + { + if (!$condition) { + throw (is_string($exception) ? new $exception(...$parameters) : $exception); + } + + return $condition; + } +} + +if (!function_exists('tap')) { + /** + * 对一个值调用给定的闭包,然后返回该值 + * + * @param mixed $value + * @param callable|null $callback + * @return mixed + */ + function tap($value, $callback = null) + { + if (is_null($callback)) { + return $value; + } + + $callback($value); + + return $value; + } +} + +if (!function_exists('value')) { + /** + * Return the default value of the given value. + * + * @param mixed $value + * @return mixed + */ + function value($value) + { + return $value instanceof Closure ? $value() : $value; + } +} + +if (!function_exists('collect')) { + /** + * Create a collection from the given value. + * + * @param mixed $value + * @return Collection + */ + function collect($value = null) + { + return new Collection($value); + } +} + +if (!function_exists('data_fill')) { + /** + * Fill in data where it's missing. + * + * @param mixed $target + * @param string|array $key + * @param mixed $value + * @return mixed + */ + function data_fill(&$target, $key, $value) + { + return data_set($target, $key, $value, false); + } +} + +if (!function_exists('data_get')) { + /** + * Get an item from an array or object using "dot" notation. + * + * @param mixed $target + * @param string|array|int $key + * @param mixed $default + * @return mixed + */ + function data_get($target, $key, $default = null) + { + if (is_null($key)) { + return $target; + } + + $key = is_array($key) ? $key : explode('.', $key); + + while (!is_null($segment = array_shift($key))) { + if ('*' === $segment) { + if ($target instanceof Collection) { + $target = $target->all(); + } elseif (!is_array($target)) { + return value($default); + } + + $result = []; + + foreach ($target as $item) { + $result[] = data_get($item, $key); + } + + return in_array('*', $key) ? Arr::collapse($result) : $result; + } + + if (Arr::accessible($target) && Arr::exists($target, $segment)) { + $target = $target[$segment]; + } elseif (is_object($target) && isset($target->{$segment})) { + $target = $target->{$segment}; + } else { + return value($default); + } + } + + return $target; + } +} + +if (!function_exists('data_set')) { + /** + * Set an item on an array or object using dot notation. + * + * @param mixed $target + * @param string|array $key + * @param mixed $value + * @param bool $overwrite + * @return mixed + */ + function data_set(&$target, $key, $value, $overwrite = true) + { + $segments = is_array($key) ? $key : explode('.', $key); + + if (($segment = array_shift($segments)) === '*') { + if (!Arr::accessible($target)) { + $target = []; + } + + if ($segments) { + foreach ($target as &$inner) { + data_set($inner, $segments, $value, $overwrite); + } + } elseif ($overwrite) { + foreach ($target as &$inner) { + $inner = $value; + } + } + } elseif (Arr::accessible($target)) { + if ($segments) { + if (!Arr::exists($target, $segment)) { + $target[$segment] = []; + } + + data_set($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite || !Arr::exists($target, $segment)) { + $target[$segment] = $value; + } + } elseif (is_object($target)) { + if ($segments) { + if (!isset($target->{$segment})) { + $target->{$segment} = []; + } + + data_set($target->{$segment}, $segments, $value, $overwrite); + } elseif ($overwrite || !isset($target->{$segment})) { + $target->{$segment} = $value; + } + } else { + $target = []; + + if ($segments) { + data_set($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite) { + $target[$segment] = $value; + } + } + + return $target; + } +} + +if (!function_exists('trait_uses_recursive')) { + /** + * 获取一个trait里所有引用到的trait + * + * @param string $trait Trait + * @return array + */ + function trait_uses_recursive(string $trait): array + { + $traits = class_uses($trait); + foreach ($traits as $trait) { + $traits += trait_uses_recursive($trait); + } + + return $traits; + } +} + +if (!function_exists('class_basename')) { + /** + * 获取类名(不包含命名空间) + * + * @param mixed $class 类名 + * @return string + */ + function class_basename($class): string + { + $class = is_object($class) ? get_class($class) : $class; + return basename(str_replace('\\', '/', $class)); + } +} + +if (!function_exists('class_uses_recursive')) { + /** + *获取一个类里所有用到的trait,包括父类的 + * + * @param mixed $class 类名 + * @return array + */ + function class_uses_recursive($class): array + { + if (is_object($class)) { + $class = get_class($class); + } + + $results = []; + $classes = array_merge([$class => $class], class_parents($class)); + foreach ($classes as $class) { + $results += trait_uses_recursive($class); + } + + return array_unique($results); + } +} diff --git a/vendor/topthink/think-helper/src/helper/Arr.php b/vendor/topthink/think-helper/src/helper/Arr.php new file mode 100644 index 0000000..ed4d6a9 --- /dev/null +++ b/vendor/topthink/think-helper/src/helper/Arr.php @@ -0,0 +1,634 @@ + +// +---------------------------------------------------------------------- + +namespace think\helper; + +use ArrayAccess; +use InvalidArgumentException; +use think\Collection; + +class Arr +{ + + /** + * Determine whether the given value is array accessible. + * + * @param mixed $value + * @return bool + */ + public static function accessible($value) + { + return is_array($value) || $value instanceof ArrayAccess; + } + + /** + * Add an element to an array using "dot" notation if it doesn't exist. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + public static function add($array, $key, $value) + { + if (is_null(static::get($array, $key))) { + static::set($array, $key, $value); + } + + return $array; + } + + /** + * Collapse an array of arrays into a single array. + * + * @param array $array + * @return array + */ + public static function collapse($array) + { + $results = []; + + foreach ($array as $values) { + if ($values instanceof Collection) { + $values = $values->all(); + } elseif (!is_array($values)) { + continue; + } + + $results = array_merge($results, $values); + } + + return $results; + } + + /** + * Cross join the given arrays, returning all possible permutations. + * + * @param array ...$arrays + * @return array + */ + public static function crossJoin(...$arrays) + { + $results = [[]]; + + foreach ($arrays as $index => $array) { + $append = []; + + foreach ($results as $product) { + foreach ($array as $item) { + $product[$index] = $item; + + $append[] = $product; + } + } + + $results = $append; + } + + return $results; + } + + /** + * Divide an array into two arrays. One with keys and the other with values. + * + * @param array $array + * @return array + */ + public static function divide($array) + { + return [array_keys($array), array_values($array)]; + } + + /** + * Flatten a multi-dimensional associative array with dots. + * + * @param array $array + * @param string $prepend + * @return array + */ + public static function dot($array, $prepend = '') + { + $results = []; + + foreach ($array as $key => $value) { + if (is_array($value) && !empty($value)) { + $results = array_merge($results, static::dot($value, $prepend . $key . '.')); + } else { + $results[$prepend . $key] = $value; + } + } + + return $results; + } + + /** + * Get all of the given array except for a specified array of keys. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function except($array, $keys) + { + static::forget($array, $keys); + + return $array; + } + + /** + * Determine if the given key exists in the provided array. + * + * @param \ArrayAccess|array $array + * @param string|int $key + * @return bool + */ + public static function exists($array, $key) + { + if ($array instanceof ArrayAccess) { + return $array->offsetExists($key); + } + + return array_key_exists($key, $array); + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function first($array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + if (empty($array)) { + return value($default); + } + + foreach ($array as $item) { + return $item; + } + } + + foreach ($array as $key => $value) { + if (call_user_func($callback, $value, $key)) { + return $value; + } + } + + return value($default); + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function last($array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + return empty($array) ? value($default) : end($array); + } + + return static::first(array_reverse($array, true), $callback, $default); + } + + /** + * Flatten a multi-dimensional array into a single level. + * + * @param array $array + * @param int $depth + * @return array + */ + public static function flatten($array, $depth = INF) + { + $result = []; + + foreach ($array as $item) { + $item = $item instanceof Collection ? $item->all() : $item; + + if (!is_array($item)) { + $result[] = $item; + } elseif ($depth === 1) { + $result = array_merge($result, array_values($item)); + } else { + $result = array_merge($result, static::flatten($item, $depth - 1)); + } + } + + return $result; + } + + /** + * Remove one or many array items from a given array using "dot" notation. + * + * @param array $array + * @param array|string $keys + * @return void + */ + public static function forget(&$array, $keys) + { + $original = &$array; + + $keys = (array) $keys; + + if (count($keys) === 0) { + return; + } + + foreach ($keys as $key) { + // if the exact key exists in the top-level, remove it + if (static::exists($array, $key)) { + unset($array[$key]); + + continue; + } + + $parts = explode('.', $key); + + // clean up before each pass + $array = &$original; + + while (count($parts) > 1) { + $part = array_shift($parts); + + if (isset($array[$part]) && is_array($array[$part])) { + $array = &$array[$part]; + } else { + continue 2; + } + } + + unset($array[array_shift($parts)]); + } + } + + /** + * Get an item from an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function get($array, $key, $default = null) + { + if (!static::accessible($array)) { + return value($default); + } + + if (is_null($key)) { + return $array; + } + + if (static::exists($array, $key)) { + return $array[$key]; + } + + if (strpos($key, '.') === false) { + return $array[$key] ?? value($default); + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($array) && static::exists($array, $segment)) { + $array = $array[$segment]; + } else { + return value($default); + } + } + + return $array; + } + + /** + * Check if an item or items exist in an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|array $keys + * @return bool + */ + public static function has($array, $keys) + { + $keys = (array) $keys; + + if (!$array || $keys === []) { + return false; + } + + foreach ($keys as $key) { + $subKeyArray = $array; + + if (static::exists($array, $key)) { + continue; + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) { + $subKeyArray = $subKeyArray[$segment]; + } else { + return false; + } + } + } + + return true; + } + + /** + * Determines if an array is associative. + * + * An array is "associative" if it doesn't have sequential numerical keys beginning with zero. + * + * @param array $array + * @return bool + */ + public static function isAssoc(array $array) + { + $keys = array_keys($array); + + return array_keys($keys) !== $keys; + } + + /** + * Get a subset of the items from the given array. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function only($array, $keys) + { + return array_intersect_key($array, array_flip((array) $keys)); + } + + /** + * Pluck an array of values from an array. + * + * @param array $array + * @param string|array $value + * @param string|array|null $key + * @return array + */ + public static function pluck($array, $value, $key = null) + { + $results = []; + + [$value, $key] = static::explodePluckParameters($value, $key); + + foreach ($array as $item) { + $itemValue = data_get($item, $value); + + // If the key is "null", we will just append the value to the array and keep + // looping. Otherwise we will key the array using the value of the key we + // received from the developer. Then we'll return the final array form. + if (is_null($key)) { + $results[] = $itemValue; + } else { + $itemKey = data_get($item, $key); + + if (is_object($itemKey) && method_exists($itemKey, '__toString')) { + $itemKey = (string) $itemKey; + } + + $results[$itemKey] = $itemValue; + } + } + + return $results; + } + + /** + * Explode the "value" and "key" arguments passed to "pluck". + * + * @param string|array $value + * @param string|array|null $key + * @return array + */ + protected static function explodePluckParameters($value, $key) + { + $value = is_string($value) ? explode('.', $value) : $value; + + $key = is_null($key) || is_array($key) ? $key : explode('.', $key); + + return [$value, $key]; + } + + /** + * Push an item onto the beginning of an array. + * + * @param array $array + * @param mixed $value + * @param mixed $key + * @return array + */ + public static function prepend($array, $value, $key = null) + { + if (is_null($key)) { + array_unshift($array, $value); + } else { + $array = [$key => $value] + $array; + } + + return $array; + } + + /** + * Get a value from the array, and remove it. + * + * @param array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function pull(&$array, $key, $default = null) + { + $value = static::get($array, $key, $default); + + static::forget($array, $key); + + return $value; + } + + /** + * Get one or a specified number of random values from an array. + * + * @param array $array + * @param int|null $number + * @return mixed + * + * @throws \InvalidArgumentException + */ + public static function random($array, $number = null) + { + $requested = is_null($number) ? 1 : $number; + + $count = count($array); + + if ($requested > $count) { + throw new InvalidArgumentException( + "You requested {$requested} items, but there are only {$count} items available." + ); + } + + if (is_null($number)) { + return $array[array_rand($array)]; + } + + if ((int) $number === 0) { + return []; + } + + $keys = array_rand($array, $number); + + $results = []; + + foreach ((array) $keys as $key) { + $results[] = $array[$key]; + } + + return $results; + } + + /** + * Set an array item to a given value using "dot" notation. + * + * If no key is given to the method, the entire array will be replaced. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + public static function set(&$array, $key, $value) + { + if (is_null($key)) { + return $array = $value; + } + + $keys = explode('.', $key); + + while (count($keys) > 1) { + $key = array_shift($keys); + + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = []; + } + + $array = &$array[$key]; + } + + $array[array_shift($keys)] = $value; + + return $array; + } + + /** + * Shuffle the given array and return the result. + * + * @param array $array + * @param int|null $seed + * @return array + */ + public static function shuffle($array, $seed = null) + { + if (is_null($seed)) { + shuffle($array); + } else { + srand($seed); + + usort($array, function () { + return rand(-1, 1); + }); + } + + return $array; + } + + /** + * Sort the array using the given callback or "dot" notation. + * + * @param array $array + * @param callable|string|null $callback + * @return array + */ + public static function sort($array, $callback = null) + { + return Collection::make($array)->sort($callback)->all(); + } + + /** + * Recursively sort an array by keys and values. + * + * @param array $array + * @return array + */ + public static function sortRecursive($array) + { + foreach ($array as &$value) { + if (is_array($value)) { + $value = static::sortRecursive($value); + } + } + + if (static::isAssoc($array)) { + ksort($array); + } else { + sort($array); + } + + return $array; + } + + /** + * Convert the array into a query string. + * + * @param array $array + * @return string + */ + public static function query($array) + { + return http_build_query($array, null, '&', PHP_QUERY_RFC3986); + } + + /** + * Filter the array using the given callback. + * + * @param array $array + * @param callable $callback + * @return array + */ + public static function where($array, callable $callback) + { + return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); + } + + /** + * If the given value is not an array and not null, wrap it in one. + * + * @param mixed $value + * @return array + */ + public static function wrap($value) + { + if (is_null($value)) { + return []; + } + + return is_array($value) ? $value : [$value]; + } +} \ No newline at end of file diff --git a/vendor/topthink/think-helper/src/helper/Str.php b/vendor/topthink/think-helper/src/helper/Str.php new file mode 100644 index 0000000..7391fbd --- /dev/null +++ b/vendor/topthink/think-helper/src/helper/Str.php @@ -0,0 +1,234 @@ + +// +---------------------------------------------------------------------- +namespace think\helper; + +class Str +{ + + protected static $snakeCache = []; + + protected static $camelCache = []; + + protected static $studlyCache = []; + + /** + * 检查字符串中是否包含某些字符串 + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function contains(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ('' != $needle && mb_strpos($haystack, $needle) !== false) { + return true; + } + } + + return false; + } + + /** + * 检查字符串是否以某些字符串结尾 + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function endsWith(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ((string) $needle === static::substr($haystack, -static::length($needle))) { + return true; + } + } + + return false; + } + + /** + * 检查字符串是否以某些字符串开头 + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function startsWith(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ('' != $needle && mb_strpos($haystack, $needle) === 0) { + return true; + } + } + + return false; + } + + /** + * 获取指定长度的随机字母数字组合的字符串 + * + * @param int $length + * @param int $type + * @param string $addChars + * @return string + */ + public static function random(int $length = 6, int $type = null, string $addChars = ''): string + { + $str = ''; + switch ($type) { + case 0: + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' . $addChars; + break; + case 1: + $chars = str_repeat('0123456789', 3); + break; + case 2: + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . $addChars; + break; + case 3: + $chars = 'abcdefghijklmnopqrstuvwxyz' . $addChars; + break; + case 4: + $chars = "们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书" . $addChars; + break; + default: + $chars = 'ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789' . $addChars; + break; + } + if ($length > 10) { + $chars = $type == 1 ? str_repeat($chars, $length) : str_repeat($chars, 5); + } + if ($type != 4) { + $chars = str_shuffle($chars); + $str = substr($chars, 0, $length); + } else { + for ($i = 0; $i < $length; $i++) { + $str .= mb_substr($chars, floor(mt_rand(0, mb_strlen($chars, 'utf-8') - 1)), 1); + } + } + return $str; + } + + /** + * 字符串转小写 + * + * @param string $value + * @return string + */ + public static function lower(string $value): string + { + return mb_strtolower($value, 'UTF-8'); + } + + /** + * 字符串转大写 + * + * @param string $value + * @return string + */ + public static function upper(string $value): string + { + return mb_strtoupper($value, 'UTF-8'); + } + + /** + * 获取字符串的长度 + * + * @param string $value + * @return int + */ + public static function length(string $value): int + { + return mb_strlen($value); + } + + /** + * 截取字符串 + * + * @param string $string + * @param int $start + * @param int|null $length + * @return string + */ + public static function substr(string $string, int $start, int $length = null): string + { + return mb_substr($string, $start, $length, 'UTF-8'); + } + + /** + * 驼峰转下划线 + * + * @param string $value + * @param string $delimiter + * @return string + */ + public static function snake(string $value, string $delimiter = '_'): string + { + $key = $value; + + if (isset(static::$snakeCache[$key][$delimiter])) { + return static::$snakeCache[$key][$delimiter]; + } + + if (!ctype_lower($value)) { + $value = preg_replace('/\s+/u', '', $value); + + $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value)); + } + + return static::$snakeCache[$key][$delimiter] = $value; + } + + /** + * 下划线转驼峰(首字母小写) + * + * @param string $value + * @return string + */ + public static function camel(string $value): string + { + if (isset(static::$camelCache[$value])) { + return static::$camelCache[$value]; + } + + return static::$camelCache[$value] = lcfirst(static::studly($value)); + } + + /** + * 下划线转驼峰(首字母大写) + * + * @param string $value + * @return string + */ + public static function studly(string $value): string + { + $key = $value; + + if (isset(static::$studlyCache[$key])) { + return static::$studlyCache[$key]; + } + + $value = ucwords(str_replace(['-', '_'], ' ', $value)); + + return static::$studlyCache[$key] = str_replace(' ', '', $value); + } + + /** + * 转为首字母大写的标题格式 + * + * @param string $value + * @return string + */ + public static function title(string $value): string + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } +} diff --git a/vendor/topthink/think-multi-app/LICENSE b/vendor/topthink/think-multi-app/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/vendor/topthink/think-multi-app/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-multi-app/README.md b/vendor/topthink/think-multi-app/README.md new file mode 100644 index 0000000..a746fa7 --- /dev/null +++ b/vendor/topthink/think-multi-app/README.md @@ -0,0 +1,14 @@ +# think-multi-app + +用于ThinkPHP6+的多应用支持 + +## 安装 + +~~~ +composer require topthink/think-multi-app +~~~ + +## 使用 + +用法参考ThinkPHP6完全开发手册[多应用模式](https://www.kancloud.cn/manual/thinkphp6_0/1297876)章节。 + diff --git a/vendor/topthink/think-multi-app/composer.json b/vendor/topthink/think-multi-app/composer.json new file mode 100644 index 0000000..92d620e --- /dev/null +++ b/vendor/topthink/think-multi-app/composer.json @@ -0,0 +1,28 @@ +{ + "name": "topthink/think-multi-app", + "description": "thinkphp6 multi app support", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0.0" + }, + "autoload": { + "psr-4": { + "think\\app\\": "src" + } + }, + "extra": { + "think":{ + "services":[ + "think\\app\\Service" + ] + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/topthink/think-multi-app/src/MultiApp.php b/vendor/topthink/think-multi-app/src/MultiApp.php new file mode 100644 index 0000000..295369c --- /dev/null +++ b/vendor/topthink/think-multi-app/src/MultiApp.php @@ -0,0 +1,268 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\app; + +use Closure; +use think\App; +use think\exception\HttpException; +use think\Request; +use think\Response; + +/** + * 多应用模式支持 + */ +class MultiApp +{ + + /** @var App */ + protected $app; + + /** + * 应用名称 + * @var string + */ + protected $name; + + /** + * 应用名称 + * @var string + */ + protected $appName; + + /** + * 应用路径 + * @var string + */ + protected $path; + + public function __construct(App $app) + { + $this->app = $app; + $this->name = $this->app->http->getName(); + $this->path = $this->app->http->getPath(); + } + + /** + * 多应用解析 + * @access public + * @param Request $request + * @param Closure $next + * @return Response + */ + public function handle($request, Closure $next) + { + if (!$this->parseMultiApp()) { + return $next($request); + } + + return $this->app->middleware->pipeline('app') + ->send($request) + ->then(function ($request) use ($next) { + return $next($request); + }); + } + + /** + * 获取路由目录 + * @access protected + * @return string + */ + protected function getRoutePath(): string + { + if (is_dir($this->app->getAppPath() . 'route')) { + return $this->app->getAppPath() . 'route' . DIRECTORY_SEPARATOR; + } + + return $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR . $this->appName . DIRECTORY_SEPARATOR; + } + + /** + * 解析多应用 + * @return bool + */ + protected function parseMultiApp(): bool + { + + + + $scriptName = $this->getScriptName(); + $defaultApp = $this->app->config->get('app.default_app') ?: 'index'; + + + if ($this->name || ($scriptName && !in_array($scriptName, ['index', 'router', 'think']))) { + $appName = $this->name ?: $scriptName; + + $this->app->http->setBind(); + } else { + // 自动多应用识别 + $this->app->http->setBind(false); + $appName = null; + $this->appName = ''; + + $bind = $this->app->config->get('app.domain_bind', []); + + if (!empty($bind)) { + // 获取当前子域名 + $subDomain = $this->app->request->subDomain(); + $domain = $this->app->request->host(true); + + if (isset($bind[$domain])) { + $appName = $bind[$domain]; + $this->app->http->setBind(); + } elseif (isset($bind[$subDomain])) { + $appName = $bind[$subDomain]; + $this->app->http->setBind(); + } elseif (isset($bind['*'])) { + $appName = $bind['*']; + $this->app->http->setBind(); + } + } + + if (!$this->app->http->isBind()) { + $path = $this->app->request->pathinfo(); + $map = $this->app->config->get('app.app_map', []); + $deny = $this->app->config->get('app.deny_app_list', []); + $name = current(explode('/', $path)); + + if (strpos($name, '.')) { + $name = strstr($name, '.', true); + } + + if (isset($map[$name])) { + if ($map[$name] instanceof Closure) { + $result = call_user_func_array($map[$name], [$this->app]); + $appName = $result ?: $name; + } else { + $appName = $map[$name]; + } + + } elseif ($name && (false !== array_search($name, $map) || in_array($name, $deny))) { + throw new HttpException(404, 'app not exists:' . $name); + } elseif ($name && isset($map['*'])) { + $appName = $map['*']; + + } else { + $appName = $name ?: $defaultApp; + + $appPath = $this->path ?: $this->app->getBasePath() . $appName . DIRECTORY_SEPARATOR; + + if (!is_dir($appPath)) { + $express = $this->app->config->get('app.app_express', false); + if ($express) { + + $this->setApp($defaultApp); + return true; + } else { + return false; + } + } + + } + + if ($name) { + $this->app->request->setRoot('/' . $name); + $this->app->request->setPathinfo(strpos($path, '/') ? ltrim(strstr($path, '/'), '/') : ''); + } + } + } + + + $this->setApp($appName ?: $defaultApp); + + + return true; + } + + /** + * 获取当前运行入口名称 + * @access protected + * @codeCoverageIgnore + * @return string + */ + protected function getScriptName(): string + { + if (isset($_SERVER['SCRIPT_FILENAME'])) { + $file = $_SERVER['SCRIPT_FILENAME']; + } elseif (isset($_SERVER['argv'][0])) { + $file = realpath($_SERVER['argv'][0]); + } + + return isset($file) ? pathinfo($file, PATHINFO_FILENAME) : ''; + } + + /** + * 设置应用 + * @param string $appName + */ + protected function setApp(string $appName): void + { + $this->appName = $appName; + $this->app->http->name($appName); + + $appPath = $this->path ?: $this->app->getBasePath() . $appName . DIRECTORY_SEPARATOR; + + $this->app->setAppPath($appPath); + // 设置应用命名空间 + $this->app->setNamespace($this->app->config->get('app.app_namespace') ?: 'app\\' . $appName); + + if (is_dir($appPath)) { + $this->app->setRuntimePath($this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . $appName . DIRECTORY_SEPARATOR); + $this->app->http->setRoutePath($this->getRoutePath()); + + //加载应用 + $this->loadApp($appName, $appPath); + } + } + + /** + * 加载应用文件 + * @param string $appName 应用名 + * @return void + */ + protected function loadApp(string $appName, string $appPath): void + { + if (is_file($appPath . 'common.php')) { + include_once $appPath . 'common.php'; + } + + $configPath = $this->app->getConfigPath(); + + $files = []; + + if (is_dir($appPath . 'config')) { + $files = array_merge($files, glob($appPath . 'config' . DIRECTORY_SEPARATOR . '*' . $this->app->getConfigExt())); + } elseif (is_dir($configPath . $appName)) { + $files = array_merge($files, glob($configPath . $appName . DIRECTORY_SEPARATOR . '*' . $this->app->getConfigExt())); + } + + foreach ($files as $file) { + $this->app->config->load($file, pathinfo($file, PATHINFO_FILENAME)); + } + + if (is_file($appPath . 'event.php')) { + $this->app->loadEvent(include $appPath . 'event.php'); + } + + if (is_file($appPath . 'middleware.php')) { + $this->app->middleware->import(include $appPath . 'middleware.php', 'app'); + } + + if (is_file($appPath . 'provider.php')) { + $this->app->bind(include $appPath . 'provider.php'); + } + + // 加载应用默认语言包 + $this->app->loadLangPack($this->app->lang->defaultLangSet()); + } + +} diff --git a/vendor/topthink/think-multi-app/src/Service.php b/vendor/topthink/think-multi-app/src/Service.php new file mode 100644 index 0000000..ad576c3 --- /dev/null +++ b/vendor/topthink/think-multi-app/src/Service.php @@ -0,0 +1,29 @@ + +// +---------------------------------------------------------------------- +namespace think\app; + +use think\Service as BaseService; + +class Service extends BaseService +{ + public function register() + { + $this->app->middleware->unshift(MultiApp::class); + + $this->commands([ + 'build' => command\Build::class, + ]); + + $this->app->bind([ + 'think\route\Url' => Url::class, + ]); + } +} diff --git a/vendor/topthink/think-multi-app/src/Url.php b/vendor/topthink/think-multi-app/src/Url.php new file mode 100644 index 0000000..78a8804 --- /dev/null +++ b/vendor/topthink/think-multi-app/src/Url.php @@ -0,0 +1,224 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\app; + +use think\App; +use think\Route; +use think\route\Url as UrlBuild; + +/** + * 路由地址生成 + */ +class Url extends UrlBuild +{ + + /** + * 直接解析URL地址 + * @access protected + * @param string $url URL + * @param string|bool $domain Domain + * @return string + */ + protected function parseUrl(string $url, &$domain): string + { + $request = $this->app->request; + + if (0 === strpos($url, '/')) { + // 直接作为路由地址解析 + $url = substr($url, 1); + } elseif (false !== strpos($url, '\\')) { + // 解析到类 + $url = ltrim(str_replace('\\', '/', $url), '/'); + } elseif (0 === strpos($url, '@')) { + // 解析到控制器 + $url = substr($url, 1); + } elseif ('' === $url) { + $url = $this->app->http->getName() . '/' . $request->controller() . '/' . $request->action(); + } else { + // 解析到 应用/控制器/操作 + $controller = $request->controller(); + $app = $this->app->http->getName(); + + $path = explode('/', $url); + $action = array_pop($path); + $controller = empty($path) ? $controller : array_pop($path); + $app = empty($path) ? $app : array_pop($path); + + $url = $controller . '/' . $action; + + $bind = $this->app->config->get('app.domain_bind', []); + + if ($key = array_search($app, $bind)) { + $domain = is_bool($domain) ? $key : $domain; + } else { + $map = $this->app->config->get('app.app_map', []); + + if ($key = array_search($app, $map)) { + $url = $key . '/' . $url; + } else { + $url = $app . '/' . $url; + } + } + + } + + return $url; + } + + public function build() + { + // 解析URL + $url = $this->url; + $suffix = $this->suffix; + $domain = $this->domain; + $request = $this->app->request; + $vars = $this->vars; + + if (0 === strpos($url, '[') && $pos = strpos($url, ']')) { + // [name] 表示使用路由命名标识生成URL + $name = substr($url, 1, $pos - 1); + $url = 'name' . substr($url, $pos + 1); + } + + if (false === strpos($url, '://') && 0 !== strpos($url, '/')) { + $info = parse_url($url); + $url = !empty($info['path']) ? $info['path'] : ''; + + if (isset($info['fragment'])) { + // 解析锚点 + $anchor = $info['fragment']; + + if (false !== strpos($anchor, '?')) { + // 解析参数 + list($anchor, $info['query']) = explode('?', $anchor, 2); + } + + if (false !== strpos($anchor, '@')) { + // 解析域名 + list($anchor, $domain) = explode('@', $anchor, 2); + } + } elseif (strpos($url, '@') && false === strpos($url, '\\')) { + // 解析域名 + list($url, $domain) = explode('@', $url, 2); + } + } + + if ($url) { + $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : ''); + $checkDomain = $domain && is_string($domain) ? $domain : null; + + $rule = $this->route->getName($checkName, $checkDomain); + + if (empty($rule) && isset($info['query'])) { + $rule = $this->route->getName($url, $checkDomain); + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + unset($info['query']); + } + } + + if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) { + // 匹配路由命名标识 + $url = $match[0]; + + if ($domain && !empty($match[1])) { + $domain = $match[1]; + } + + if (!is_null($match[2])) { + $suffix = $match[2]; + } + + if (!$this->app->http->isBind()) { + $url = $this->app->http->getName() . '/' . $url; + } + } elseif (!empty($rule) && isset($name)) { + throw new \InvalidArgumentException('route name not exists:' . $name); + } else { + // 检测URL绑定 + $bind = $this->route->getDomainBind($domain && is_string($domain) ? $domain : null); + + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } else { + $binds = $this->route->getBind(); + + foreach ($binds as $key => $val) { + if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) { + $url = substr($url, strlen($val) + 1); + $domain = $key; + break; + } + } + } + + // 路由标识不存在 直接解析 + $url = $this->parseUrl($url, $domain); + + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + } + } + + // 还原URL分隔符 + $depr = $this->route->config('pathinfo_depr'); + $url = str_replace('/', $depr, $url); + + $file = $request->baseFile(); + if ($file && 0 !== strpos($request->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + + $url = rtrim($file, '/') . '/' . ltrim($url, '/'); + + // URL后缀 + if ('/' == substr($url, -1) || '' == $url) { + $suffix = ''; + } else { + $suffix = $this->parseSuffix($suffix); + } + + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; + + // 参数组装 + if (!empty($vars)) { + // 添加参数 + if ($this->route->config('url_common_param')) { + $vars = http_build_query($vars); + $url .= $suffix . '?' . $vars . $anchor; + } else { + foreach ($vars as $var => $val) { + $val = (string) $val; + if ('' !== $val) { + $url .= $depr . $var . $depr . urlencode($val); + } + } + + $url .= $suffix . $anchor; + } + } else { + $url .= $suffix . $anchor; + } + + // 检测域名 + $domain = $this->parseDomain($url, $domain); + + // URL组装 + return $domain . rtrim($this->root, '/') . '/' . ltrim($url, '/'); + } + +} diff --git a/vendor/topthink/think-multi-app/src/command/Build.php b/vendor/topthink/think-multi-app/src/command/Build.php new file mode 100644 index 0000000..e192167 --- /dev/null +++ b/vendor/topthink/think-multi-app/src/command/Build.php @@ -0,0 +1,180 @@ + +// +---------------------------------------------------------------------- + +namespace think\app\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; + +class Build extends Command +{ + /** + * 应用基础目录 + * @var string + */ + protected $basePath; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('build') + ->addArgument('app', Argument::OPTIONAL, 'app name .') + ->setDescription('Build App Dirs'); + } + + protected function execute(Input $input, Output $output) + { + $this->basePath = $this->app->getBasePath(); + $app = $input->getArgument('app') ?: ''; + + if (is_file($this->basePath . 'build.php')) { + $list = include $this->basePath . 'build.php'; + } else { + $list = [ + '__dir__' => ['controller', 'model', 'view'], + ]; + } + + $this->buildApp($app, $list); + $output->writeln("Successed"); + + } + + /** + * 创建应用 + * @access protected + * @param string $app 应用名 + * @param array $list 目录结构 + * @return void + */ + protected function buildApp(string $app, array $list = []): void + { + if (!is_dir($this->basePath . $app)) { + // 创建应用目录 + mkdir($this->basePath . $app); + } + + $appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : ''); + $namespace = 'app' . ($app ? '\\' . $app : ''); + + // 创建配置文件和公共文件 + $this->buildCommon($app); + // 创建模块的默认页面 + $this->buildHello($app, $namespace); + + foreach ($list as $path => $file) { + if ('__dir__' == $path) { + // 生成子目录 + foreach ($file as $dir) { + $this->checkDirBuild($appPath . $dir); + } + } elseif ('__file__' == $path) { + // 生成(空白)文件 + foreach ($file as $name) { + if (!is_file($appPath . $name)) { + file_put_contents($appPath . $name, 'php' == pathinfo($name, PATHINFO_EXTENSION) ? 'app->config->get('route.controller_suffix')) { + $filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . 'Controller.php'; + $class = $val . 'Controller'; + } + $content = "checkDirBuild(dirname($filename)); + $content = ''; + break; + default: + // 其他文件 + $content = "app->config->get('route.controller_suffix') ? 'Controller' : ''; + $filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . $suffix . '.php'; + + if (!is_file($filename)) { + $content = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'controller.stub'); + $content = str_replace(['{%name%}', '{%app%}', '{%layer%}', '{%suffix%}'], [$app, $namespace, 'controller', $suffix], $content); + $this->checkDirBuild(dirname($filename)); + + file_put_contents($filename, $content); + } + } + + /** + * 创建应用的公共文件 + * @access protected + * @param string $app 目录 + * @return void + */ + protected function buildCommon(string $app): void + { + $appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : ''); + + if (!is_file($appPath . 'common.php')) { + file_put_contents($appPath . 'common.php', "=7.1.0", + "ext-json": "*", + "psr/simple-cache": "^1.0", + "psr/log": "~1.0", + "topthink/think-helper":"^3.1" + }, + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [] + } +} diff --git a/vendor/topthink/think-orm/src/DbManager.php b/vendor/topthink/think-orm/src/DbManager.php new file mode 100644 index 0000000..ceb508f --- /dev/null +++ b/vendor/topthink/think-orm/src/DbManager.php @@ -0,0 +1,406 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use Psr\Log\LoggerInterface; +use Psr\SimpleCache\CacheInterface; +use think\db\BaseQuery; +use think\db\ConnectionInterface; +use think\db\Query; +use think\db\Raw; + +/** + * Class DbManager + * @package think + * @mixin BaseQuery + * @mixin Query + */ +class DbManager +{ + /** + * 数据库连接实例 + * @var array + */ + protected $instance = []; + + /** + * 数据库配置 + * @var array + */ + protected $config = []; + + /** + * Event对象或者数组 + * @var array|object + */ + protected $event; + + /** + * SQL监听 + * @var array + */ + protected $listen = []; + + /** + * SQL日志 + * @var array + */ + protected $dbLog = []; + + /** + * 查询次数 + * @var int + */ + protected $queryTimes = 0; + + /** + * 查询缓存对象 + * @var CacheInterface + */ + protected $cache; + + /** + * 查询日志对象 + * @var LoggerInterface + */ + protected $log; + + /** + * 架构函数 + * @access public + */ + public function __construct() + { + $this->modelMaker(); + } + + /** + * 注入模型对象 + * @access public + * @return void + */ + protected function modelMaker() + { + $this->triggerSql(); + + Model::setDb($this); + + if (is_object($this->event)) { + Model::setEvent($this->event); + } + + Model::maker(function (Model $model) { + $isAutoWriteTimestamp = $model->getAutoWriteTimestamp(); + + if (is_null($isAutoWriteTimestamp)) { + // 自动写入时间戳 + $model->isAutoWriteTimestamp($this->getConfig('auto_timestamp', true)); + } + + $dateFormat = $model->getDateFormat(); + + if (is_null($dateFormat)) { + // 设置时间戳格式 + $model->setDateFormat($this->getConfig('datetime_format', 'Y-m-d H:i:s')); + } + }); + } + + /** + * 监听SQL + * @access protected + * @return void + */ + protected function triggerSql(): void + { + // 监听SQL + $this->listen(function ($sql, $time, $master) { + if (0 === strpos($sql, 'CONNECT:')) { + $this->log($sql); + return; + } + + // 记录SQL + if (is_bool($master)) { + // 分布式记录当前操作的主从 + $master = $master ? 'master|' : 'slave|'; + } else { + $master = ''; + } + + $this->log($sql . ' [ ' . $master . 'RunTime:' . $time . 's ]'); + }); + } + + /** + * 初始化配置参数 + * @access public + * @param array $config 连接配置 + * @return void + */ + public function setConfig($config): void + { + $this->config = $config; + } + + /** + * 设置缓存对象 + * @access public + * @param CacheInterface $cache 缓存对象 + * @return void + */ + public function setCache(CacheInterface $cache): void + { + $this->cache = $cache; + } + + /** + * 设置日志对象 + * @access public + * @param LoggerInterface $log 日志对象 + * @return void + */ + public function setLog(LoggerInterface $log): void + { + $this->log = $log; + } + + /** + * 记录SQL日志 + * @access protected + * @param string $log SQL日志信息 + * @param string $type 日志类型 + * @return void + */ + public function log(string $log, string $type = 'sql') + { + if ($this->log) { + $this->log->log($type, $log); + } else { + $this->dbLog[$type][] = $log; + } + } + + /** + * 获得查询日志(没有设置日志对象使用) + * @access public + * @param bool $clear 是否清空 + * @return array + */ + public function getDbLog(bool $clear = false): array + { + $logs = $this->dbLog; + if ($clear) { + $this->dbLog = []; + } + + return $logs; + } + + /** + * 获取配置参数 + * @access public + * @param string $name 配置参数 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = '', $default = null) + { + if ('' === $name) { + return $this->config; + } + + return $this->config[$name] ?? $default; + } + + /** + * 创建/切换数据库连接查询 + * @access public + * @param string|null $name 连接配置标识 + * @param bool $force 强制重新连接 + * @return BaseQuery + */ + public function connect(string $name = null, bool $force = false): BaseQuery + { + $connection = $this->instance($name, $force); + + $class = $connection->getQueryClass(); + $query = new $class($connection); + + $timeRule = $this->getConfig('time_query_rule'); + if (!empty($timeRule)) { + $query->timeRule($timeRule); + } + + return $query; + } + + /** + * 创建数据库连接实例 + * @access protected + * @param string|null $name 连接标识 + * @param bool $force 强制重新连接 + * @return ConnectionInterface + */ + protected function instance(string $name = null, bool $force = false): ConnectionInterface + { + if (empty($name)) { + $name = $this->getConfig('default', 'mysql'); + } + + if ($force || !isset($this->instance[$name])) { + $this->instance[$name] = $this->createConnection($name); + } + + return $this->instance[$name]; + } + + /** + * 获取连接配置 + * @param string $name + * @return array + */ + protected function getConnectionConfig(string $name): array + { + $connections = $this->getConfig('connections'); + if (!isset($connections[$name])) { + throw new InvalidArgumentException('Undefined db config:' . $name); + } + + return $connections[$name]; + } + + /** + * 创建连接 + * @param $name + * @return ConnectionInterface + */ + protected function createConnection(string $name): ConnectionInterface + { + $config = $this->getConnectionConfig($name); + + $type = !empty($config['type']) ? $config['type'] : 'mysql'; + + if (false !== strpos($type, '\\')) { + $class = $type; + } else { + $class = '\\think\\db\\connector\\' . ucfirst($type); + } + + /** @var ConnectionInterface $connection */ + $connection = new $class($config); + $connection->setDb($this); + + if ($this->cache) { + $connection->setCache($this->cache); + } + + return $connection; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $value 表达式 + * @return Raw + */ + public function raw(string $value): Raw + { + return new Raw($value); + } + + /** + * 更新查询次数 + * @access public + * @return void + */ + public function updateQueryTimes(): void + { + $this->queryTimes++; + } + + /** + * 重置查询次数 + * @access public + * @return void + */ + public function clearQueryTimes(): void + { + $this->queryTimes = 0; + } + + /** + * 获得查询次数 + * @access public + * @return integer + */ + public function getQueryTimes(): int + { + return $this->queryTimes; + } + + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen(callable $callback): void + { + $this->listen[] = $callback; + } + + /** + * 获取监听SQL执行 + * @access public + * @return array + */ + public function getListen(): array + { + return $this->listen; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public function event(string $event, callable $callback): void + { + $this->event[$event][] = $callback; + } + + /** + * 触发事件 + * @access public + * @param string $event 事件名 + * @param mixed $params 传入参数 + * @return mixed + */ + public function trigger(string $event, $params = null) + { + if (isset($this->event[$event])) { + foreach ($this->event[$event] as $callback) { + call_user_func_array($callback, [$this]); + } + } + } + + public function __call($method, $args) + { + return call_user_func_array([$this->connect(), $method], $args); + } +} diff --git a/vendor/topthink/think-orm/src/Model.php b/vendor/topthink/think-orm/src/Model.php new file mode 100644 index 0000000..9609478 --- /dev/null +++ b/vendor/topthink/think-orm/src/Model.php @@ -0,0 +1,993 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use Closure; +use JsonSerializable; +use think\contract\Arrayable; +use think\contract\Jsonable; +use think\db\BaseQuery as Query; + +/** + * Class Model + * @package think + * @mixin Query + * @method void onAfterRead(Model $model) static after_read事件定义 + * @method mixed onBeforeInsert(Model $model) static before_insert事件定义 + * @method void onAfterInsert(Model $model) static after_insert事件定义 + * @method mixed onBeforeUpdate(Model $model) static before_update事件定义 + * @method void onAfterUpdate(Model $model) static after_update事件定义 + * @method mixed onBeforeWrite(Model $model) static before_write事件定义 + * @method void onAfterWrite(Model $model) static after_write事件定义 + * @method mixed onBeforeDelete(Model $model) static before_write事件定义 + * @method void onAfterDelete(Model $model) static after_delete事件定义 + * @method void onBeforeRestore(Model $model) static before_restore事件定义 + * @method void onAfterRestore(Model $model) static after_restore事件定义 + */ +abstract class Model implements JsonSerializable, ArrayAccess, Arrayable, Jsonable +{ + use model\concern\Attribute; + use model\concern\RelationShip; + use model\concern\ModelEvent; + use model\concern\TimeStamp; + use model\concern\Conversion; + + /** + * 数据是否存在 + * @var bool + */ + private $exists = false; + + /** + * 是否强制更新所有数据 + * @var bool + */ + private $force = false; + + /** + * 是否Replace + * @var bool + */ + private $replace = false; + + /** + * 数据表后缀 + * @var string + */ + protected $suffix; + + /** + * 更新条件 + * @var array + */ + private $updateWhere; + + /** + * 数据库配置 + * @var string + */ + protected $connection; + + /** + * 模型名称 + * @var string + */ + protected $name; + + /** + * 主键值 + * @var string + */ + protected $key; + + /** + * 数据表名称 + * @var string + */ + protected $table; + + /** + * 初始化过的模型. + * @var array + */ + protected static $initialized = []; + + /** + * 软删除字段默认值 + * @var mixed + */ + protected $defaultSoftDelete; + + /** + * 全局查询范围 + * @var array + */ + protected $globalScope = []; + + /** + * 延迟保存信息 + * @var bool + */ + private $lazySave = false; + + /** + * Db对象 + * @var DbManager + */ + protected static $db; + + /** + * 容器对象的依赖注入方法 + * @var callable + */ + protected static $invoker; + + /** + * 服务注入 + * @var Closure[] + */ + protected static $maker = []; + + /** + * 设置服务注入 + * @access public + * @param Closure $maker + * @return void + */ + public static function maker(Closure $maker) + { + static::$maker[] = $maker; + } + + /** + * 设置Db对象 + * @access public + * @param DbManager $db Db对象 + * @return void + */ + public static function setDb(DbManager $db) + { + self::$db = $db; + } + + /** + * 设置容器对象的依赖注入方法 + * @access public + * @param callable $callable 依赖注入方法 + * @return void + */ + public static function setInvoker(callable $callable): void + { + self::$invoker = $callable; + } + + /** + * 调用反射执行模型方法 支持参数绑定 + * @access public + * @param mixed $method + * @param array $vars 参数 + * @return mixed + */ + public function invoke($method, array $vars = []) + { + if (self::$invoker) { + $call = self::$invoker; + return $call($method instanceof Closure ? $method : Closure::fromCallable([$this, $method]), $vars); + } + + return call_user_func_array($method instanceof Closure ? $method : [$this, $method], $vars); + } + + /** + * 架构函数 + * @access public + * @param array $data 数据 + */ + public function __construct(array $data = []) + { + $this->data = $data; + + if (!empty($this->data)) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $this->data)) { + unset($this->data[$key]); + } + } + } + + // 记录原始数据 + $this->origin = $this->data; + + if (empty($this->name)) { + // 当前模型名 + $name = str_replace('\\', '/', static::class); + $this->name = basename($name); + } + + if (!empty(static::$maker)) { + foreach (static::$maker as $maker) { + call_user_func($maker, $this); + } + } + + // 执行初始化操作 + $this->initialize(); + } + + /** + * 获取当前模型名称 + * @access public + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * 创建新的模型实例 + * @access public + * @param array $data 数据 + * @param mixed $where 更新条件 + * @return Model + */ + public function newInstance(array $data = [], $where = null): Model + { + if (empty($data)) { + return new static(); + } + + $model = (new static($data))->exists(true); + $model->setUpdateWhere($where); + + $model->trigger('AfterRead'); + + return $model; + } + + /** + * 设置模型的更新条件 + * @access protected + * @param mixed $where 更新条件 + * @return void + */ + protected function setUpdateWhere($where): void + { + $this->updateWhere = $where; + } + + /** + * 设置当前模型数据表的后缀 + * @access public + * @param string $suffix 数据表后缀 + * @return $this + */ + public function setSuffix(string $suffix) + { + $this->suffix = $suffix; + return $this; + } + + /** + * 获取当前模型的数据表后缀 + * @access public + * @return string + */ + public function getSuffix(): string + { + return $this->suffix ?: ''; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param array $scope 设置不使用的全局查询范围 + * @return Query + */ + public function db($scope = []): Query + { + /** @var Query $query */ + $query = self::$db->connect($this->connection) + ->name($this->name . $this->suffix) + ->pk($this->pk); + + if (!empty($this->table)) { + $query->table($this->table . $this->suffix); + } + + $query->model($this) + ->json($this->json, $this->jsonAssoc) + ->setFieldType(array_merge($this->schema, $this->jsonType)); + + // 软删除 + if (property_exists($this, 'withTrashed') && !$this->withTrashed) { + $this->withNoTrashed($query); + } + + // 全局作用域 + if (is_array($scope)) { + $globalScope = array_diff($this->globalScope, $scope); + $query->scope($globalScope); + } + + // 返回当前模型的数据库查询对象 + return $query; + } + + /** + * 初始化模型 + * @access private + * @return void + */ + private function initialize(): void + { + if (!isset(static::$initialized[static::class])) { + static::$initialized[static::class] = true; + static::init(); + } + } + + /** + * 初始化处理 + * @access protected + * @return void + */ + protected static function init() + { + } + + protected function checkData(): void + { + } + + protected function checkResult($result): void + { + } + + /** + * 更新是否强制写入数据 而不做比较(亦可用于软删除的强制删除) + * @access public + * @param bool $force + * @return $this + */ + public function force(bool $force = true) + { + $this->force = $force; + return $this; + } + + /** + * 判断force + * @access public + * @return bool + */ + public function isForce(): bool + { + return $this->force; + } + + /** + * 新增数据是否使用Replace + * @access public + * @param bool $replace + * @return $this + */ + public function replace(bool $replace = true) + { + $this->replace = $replace; + return $this; + } + + /** + * 刷新模型数据 + * @access public + * @param bool $relation 是否刷新关联数据 + * @return $this + */ + public function refresh(bool $relation = false) + { + if ($this->exists) { + $this->data = $this->db()->find($this->getKey())->getData(); + $this->origin = $this->data; + + if ($relation) { + $this->relation = []; + } + } + + return $this; + } + + /** + * 设置数据是否存在 + * @access public + * @param bool $exists + * @return $this + */ + public function exists(bool $exists = true) + { + $this->exists = $exists; + return $this; + } + + /** + * 判断数据是否存在数据库 + * @access public + * @return bool + */ + public function isExists(): bool + { + return $this->exists; + } + + /** + * 判断模型是否为空 + * @access public + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->data); + } + + /** + * 延迟保存当前数据对象 + * @access public + * @param array|bool $data 数据 + * @return void + */ + public function lazySave($data = []): void + { + if (false === $data) { + $this->lazySave = false; + } else { + if (is_array($data)) { + $this->setAttrs($data); + } + + $this->lazySave = true; + } + } + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param string $sequence 自增序列名 + * @return bool + */ + public function save(array $data = [], string $sequence = null): bool + { + // 数据对象赋值 + $this->setAttrs($data); + + if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) { + return false; + } + + $result = $this->exists ? $this->updateData() : $this->insertData($sequence); + + if (false === $result) { + return false; + } + + // 写入回调 + $this->trigger('AfterWrite'); + + // 重新记录原始数据 + $this->origin = $this->data; + $this->set = []; + $this->lazySave = false; + + return true; + } + + /** + * 检查数据是否允许写入 + * @access protected + * @return array + */ + protected function checkAllowFields(): array + { + // 检测字段 + if (empty($this->field)) { + if (!empty($this->schema)) { + $this->field = array_keys(array_merge($this->schema, $this->jsonType)); + } else { + $query = $this->db(); + $table = $this->table ? $this->table . $this->suffix : $query->getTable(); + + $this->field = $query->getConnection()->getTableFields($table); + } + + return $this->field; + } + + $field = $this->field; + + if ($this->autoWriteTimestamp) { + array_push($field, $this->createTime, $this->updateTime); + } + + if (!empty($this->disuse)) { + // 废弃字段 + $field = array_diff($field, $this->disuse); + } + + return $field; + } + + /** + * 保存写入数据 + * @access protected + * @return bool + */ + protected function updateData(): bool + { + // 事件回调 + if (false === $this->trigger('BeforeUpdate')) { + return false; + } + + $this->checkData(); + + // 获取有更新的数据 + $data = $this->getChangedData(); + + if (empty($data)) { + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + return true; + } + + if ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(); + + foreach ($this->relationWrite as $name => $val) { + if (!is_array($val)) { + continue; + } + + foreach ($val as $key) { + if (isset($data[$key])) { + unset($data[$key]); + } + } + } + + // 模型更新 + $db = $this->db(); + $db->startTrans(); + + try { + $this->key = null; + $where = $this->getWhere(); + + $result = $db->where($where) + ->strict(false) + ->cache(true) + ->setOption('key', $this->key) + ->field($allowFields) + ->update($data); + + $this->checkResult($result); + + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + $db->commit(); + + // 更新回调 + $this->trigger('AfterUpdate'); + + return true; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 新增写入数据 + * @access protected + * @param string $sequence 自增名 + * @return bool + */ + protected function insertData(string $sequence = null): bool + { + // 时间戳自动写入 + if ($this->autoWriteTimestamp) { + if ($this->createTime && !isset($this->data[$this->createTime])) { + $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime); + } + + if ($this->updateTime && !isset($this->data[$this->updateTime])) { + $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + } + } + + if (false === $this->trigger('BeforeInsert')) { + return false; + } + + $this->checkData(); + + // 检查允许字段 + $allowFields = $this->checkAllowFields(); + + $db = $this->db(); + $db->startTrans(); + + try { + $result = $db->strict(false) + ->field($allowFields) + ->replace($this->replace) + ->sequence($sequence) + ->insert($this->data, true); + + // 获取自动增长主键 + if ($result) { + $pk = $this->getPk(); + + if (is_string($pk) && (!isset($this->data[$pk]) || '' == $this->data[$pk])) { + $this->data[$pk] = $result; + } + } + + // 关联写入 + if (!empty($this->relationWrite)) { + $this->autoRelationInsert(); + } + + $db->commit(); + + // 标记数据已经存在 + $this->exists = true; + + // 新增回调 + $this->trigger('AfterInsert'); + + return true; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 获取当前的更新条件 + * @access public + * @return mixed + */ + public function getWhere() + { + $pk = $this->getPk(); + + if (is_string($pk) && isset($this->data[$pk])) { + $where = [[$pk, '=', $this->data[$pk]]]; + $this->key = $this->data[$pk]; + } elseif (is_array($pk)) { + foreach ($pk as $field) { + if (isset($this->data[$field])) { + $where[] = [$field, '=', $this->data[$field]]; + } + } + } + + if (empty($where)) { + $where = empty($this->updateWhere) ? null : $this->updateWhere; + } + + return $where; + } + + /** + * 保存多个数据到当前数据对象 + * @access public + * @param iterable $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 + * @return Collection + * @throws \Exception + */ + public function saveAll(iterable $dataSet, bool $replace = true): Collection + { + $db = $this->db(); + $db->startTrans(); + + try { + $pk = $this->getPk(); + + if (is_string($pk) && $replace) { + $auto = true; + } + + $result = []; + + foreach ($dataSet as $key => $data) { + if ($this->exists || (!empty($auto) && isset($data[$pk]))) { + $result[$key] = self::update($data); + } else { + $result[$key] = self::create($data, $this->field, $this->replace); + } + } + + $db->commit(); + + return $this->toCollection($result); + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete(): bool + { + if (!$this->exists || $this->isEmpty() || false === $this->trigger('BeforeDelete')) { + return false; + } + + // 读取更新条件 + $where = $this->getWhere(); + + $db = $this->db(); + $db->startTrans(); + + try { + // 删除当前模型数据 + $db->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + + $db->commit(); + + $this->trigger('AfterDelete'); + + $this->exists = false; + $this->lazySave = false; + + return true; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 写入数据 + * @access public + * @param array $data 数据数组 + * @param array $allowField 允许字段 + * @param bool $replace 使用Replace + * @return static + */ + public static function create(array $data, array $allowField = [], bool $replace = false): Model + { + $model = new static(); + + if (!empty($allowField)) { + $model->allowField($allowField); + } + + $model->replace($replace)->save($data); + + return $model; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param mixed $where 更新条件 + * @param array $allowField 允许字段 + * @return static + */ + public static function update(array $data, $where = [], array $allowField = []) + { + $model = new static(); + + if (!empty($allowField)) { + $model->allowField($allowField); + } + + if (!empty($where)) { + $model->setUpdateWhere($where); + } + + $model->exists(true)->save($data); + + return $model; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @param bool $force 是否强制删除 + * @return bool + */ + public static function destroy($data, bool $force = false): bool + { + if (empty($data) && 0 !== $data) { + return false; + } + + $model = new static(); + + $query = $model->db(); + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + $data($query); + $data = null; + } + + $resultSet = $query->select($data); + + foreach ($resultSet as $result) { + $result->force($force)->delete(); + } + + return true; + } + + /** + * 解序列化后处理 + */ + public function __wakeup() + { + $this->initialize(); + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set(string $name, $value): void + { + $this->setAttr($name, $value); + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get(string $name) + { + return $this->getAttr($name); + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return bool + */ + public function __isset(string $name): bool + { + return !is_null($this->getAttr($name)); + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset(string $name): void + { + unset($this->data[$name], $this->relation[$name]); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->setAttr($name, $value); + } + + public function offsetExists($name): bool + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->getAttr($name); + } + + /** + * 设置不使用的全局查询范围 + * @access public + * @param array $scope 不启用的全局查询范围 + * @return Query + */ + public static function withoutGlobalScope(array $scope = null) + { + $model = new static(); + + return $model->db($scope); + } + + /** + * 切换后缀进行查询 + * @access public + * @param string $suffix 切换的表后缀 + * @return Model + */ + public static function suffix(string $suffix) + { + $model = new static(); + $model->setSuffix($suffix); + + return $model; + } + + public function __call($method, $args) + { + if ('withattr' == strtolower($method)) { + return call_user_func_array([$this, 'withAttribute'], $args); + } + + return call_user_func_array([$this->db(), $method], $args); + } + + public static function __callStatic($method, $args) + { + $model = new static(); + + return call_user_func_array([$model->db(), $method], $args); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + if ($this->lazySave) { + $this->save(); + } + } +} diff --git a/vendor/topthink/think-orm/src/Paginator.php b/vendor/topthink/think-orm/src/Paginator.php new file mode 100644 index 0000000..06999a1 --- /dev/null +++ b/vendor/topthink/think-orm/src/Paginator.php @@ -0,0 +1,497 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Closure; +use Countable; +use DomainException; +use IteratorAggregate; +use JsonSerializable; +use think\paginator\driver\Bootstrap; +use Traversable; + +/** + * 分页基础类 + * @mixin Collection + */ +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * 是否简洁模式 + * @var bool + */ + protected $simple = false; + + /** + * 数据集 + * @var Collection + */ + protected $items; + + /** + * 当前页 + * @var int + */ + protected $currentPage; + + /** + * 最后一页 + * @var int + */ + protected $lastPage; + + /** + * 数据总数 + * @var integer|null + */ + protected $total; + + /** + * 每页数量 + * @var int + */ + protected $listRows; + + /** + * 是否有下一页 + * @var bool + */ + protected $hasMore; + + /** + * 分页配置 + * @var array + */ + protected $options = [ + 'var_page' => 'page', + 'path' => '/', + 'query' => [], + 'fragment' => '', + ]; + + /** + * 获取当前页码 + * @var Closure + */ + protected static $currentPageResolver; + + /** + * 获取当前路径 + * @var Closure + */ + protected static $currentPathResolver; + + /** + * @var Closure + */ + protected static $maker; + + public function __construct($items, int $listRows, int $currentPage = 1, int $total = null, bool $simple = false, array $options = []) + { + $this->options = array_merge($this->options, $options); + + $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path']; + + $this->simple = $simple; + $this->listRows = $listRows; + + if (!$items instanceof Collection) { + $items = Collection::make($items); + } + + if ($simple) { + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = count($items) > ($this->listRows); + $items = $items->slice(0, $this->listRows); + } else { + $this->total = $total; + $this->lastPage = (int) ceil($total / $listRows); + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = $this->currentPage < $this->lastPage; + } + $this->items = $items; + } + + /** + * @access public + * @param mixed $items + * @param int $listRows + * @param int $currentPage + * @param int $total + * @param bool $simple + * @param array $options + * @return Paginator + */ + public static function make($items, int $listRows, int $currentPage = 1, int $total = null, bool $simple = false, array $options = []) + { + if (isset(static::$maker)) { + return call_user_func(static::$maker, $items, $listRows, $currentPage, $total, $simple, $options); + } + + return new Bootstrap($items, $listRows, $currentPage, $total, $simple, $options); + } + + public static function maker(Closure $resolver) + { + static::$maker = $resolver; + } + + protected function setCurrentPage(int $currentPage): int + { + if (!$this->simple && $currentPage > $this->lastPage) { + return $this->lastPage > 0 ? $this->lastPage : 1; + } + + return $currentPage; + } + + /** + * 获取页码对应的链接 + * + * @access protected + * @param int $page + * @return string + */ + protected function url(int $page): string + { + if ($page <= 0) { + $page = 1; + } + + if (strpos($this->options['path'], '[PAGE]') === false) { + $parameters = [$this->options['var_page'] => $page]; + $path = $this->options['path']; + } else { + $parameters = []; + $path = str_replace('[PAGE]', $page, $this->options['path']); + } + + if (count($this->options['query']) > 0) { + $parameters = array_merge($this->options['query'], $parameters); + } + + $url = $path; + if (!empty($parameters)) { + $url .= '?' . http_build_query($parameters, '', '&'); + } + + return $url . $this->buildFragment(); + } + + /** + * 自动获取当前页码 + * @access public + * @param string $varPage + * @param int $default + * @return int + */ + public static function getCurrentPage(string $varPage = 'page', int $default = 1): int + { + if (isset(static::$currentPageResolver)) { + return call_user_func(static::$currentPageResolver, $varPage); + } + + return $default; + } + + /** + * 设置获取当前页码闭包 + * @param Closure $resolver + */ + public static function currentPageResolver(Closure $resolver) + { + static::$currentPageResolver = $resolver; + } + + /** + * 自动获取当前的path + * @access public + * @param string $default + * @return string + */ + public static function getCurrentPath($default = '/'): string + { + if (isset(static::$currentPathResolver)) { + return call_user_func(static::$currentPathResolver); + } + + return $default; + } + + /** + * 设置获取当前路径闭包 + * @param Closure $resolver + */ + public static function currentPathResolver(Closure $resolver) + { + static::$currentPathResolver = $resolver; + } + + public function total(): int + { + if ($this->simple) { + throw new DomainException('not support total'); + } + + return $this->total; + } + + public function listRows(): int + { + return $this->listRows; + } + + public function currentPage(): int + { + return $this->currentPage; + } + + public function lastPage(): int + { + if ($this->simple) { + throw new DomainException('not support last'); + } + + return $this->lastPage; + } + + /** + * 数据是否足够分页 + * @access public + * @return bool + */ + public function hasPages(): bool + { + return !(1 == $this->currentPage && !$this->hasMore); + } + + /** + * 创建一组分页链接 + * + * @access public + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange(int $start, int $end): array + { + $urls = []; + + for ($page = $start; $page <= $end; $page++) { + $urls[$page] = $this->url($page); + } + + return $urls; + } + + /** + * 设置URL锚点 + * + * @access public + * @param string|null $fragment + * @return $this + */ + public function fragment(string $fragment = null) + { + $this->options['fragment'] = $fragment; + + return $this; + } + + /** + * 添加URL参数 + * + * @access public + * @param array $append + * @return $this + */ + public function appends(array $append) + { + foreach ($append as $k => $v) { + if ($k !== $this->options['var_page']) { + $this->options['query'][$k] = $v; + } + } + + return $this; + } + + /** + * 构造锚点字符串 + * + * @access public + * @return string + */ + protected function buildFragment(): string + { + return $this->options['fragment'] ? '#' . $this->options['fragment'] : ''; + } + + /** + * 渲染分页html + * @access public + * @return mixed + */ + abstract public function render(); + + public function items() + { + return $this->items->all(); + } + + /** + * 获取数据集 + * + * @return Collection|\think\model\Collection + */ + public function getCollection() + { + return $this->items; + } + + public function isEmpty(): bool + { + return $this->items->isEmpty(); + } + + /** + * 给每个元素执行个回调 + * + * @access public + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + + if (false === $result) { + break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * Retrieve an external iterator + * @access public + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @access public + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @access public + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @access public + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @access public + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * Count elements of an object + */ + public function count(): int + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + public function toArray(): array + { + try { + $total = $this->total(); + } catch (DomainException $e) { + $total = null; + } + + return [ + 'total' => $total, + 'per_page' => $this->listRows(), + 'current_page' => $this->currentPage(), + 'last_page' => $this->lastPage, + 'data' => $this->items->toArray(), + ]; + } + + /** + * Specify data which should be serialized to JSON + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + public function __call($name, $arguments) + { + $result = call_user_func_array([$this->items, $name], $arguments); + + if ($result instanceof Collection) { + $this->items = $result; + return $this; + } + + return $result; + } + +} diff --git a/vendor/topthink/think-orm/src/db/BaseQuery.php b/vendor/topthink/think-orm/src/db/BaseQuery.php new file mode 100644 index 0000000..1f97e63 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/BaseQuery.php @@ -0,0 +1,1270 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use think\Collection; +use think\db\exception\DataNotFoundException; +use think\db\exception\DbException as Exception; +use think\db\exception\ModelNotFoundException; +use think\helper\Str; +use think\Model; +use think\Paginator; + +/** + * 数据查询基础类 + */ +abstract class BaseQuery +{ + use concern\TimeFieldQuery; + use concern\AggregateQuery; + use concern\ModelRelationQuery; + use concern\ResultOperation; + use concern\Transaction; + use concern\WhereQuery; + + /** + * 当前数据库连接对象 + * @var Connection + */ + protected $connection; + + /** + * 当前数据表名称(不含前缀) + * @var string + */ + protected $name = ''; + + /** + * 当前数据表主键 + * @var string|array + */ + protected $pk; + + /** + * 当前数据表自增主键 + * @var string + */ + protected $autoinc; + + /** + * 当前数据表前缀 + * @var string + */ + protected $prefix = ''; + + /** + * 当前查询参数 + * @var array + */ + protected $options = []; + + /** + * 架构函数 + * @access public + * @param ConnectionInterface $connection 数据库连接对象 + */ + public function __construct(ConnectionInterface $connection) + { + $this->connection = $connection; + + $this->prefix = $this->connection->getConfig('prefix'); + } + + /** + * 利用__call方法实现一些特殊的Model方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + * @throws Exception + */ + public function __call(string $method, array $args) + { + if (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Str::snake(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Str::snake(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); + } elseif (strtolower(substr($method, 0, 7)) == 'whereor') { + $name = Str::snake(substr($method, 7)); + array_unshift($args, $name); + return call_user_func_array([$this, 'whereOr'], $args); + } elseif (strtolower(substr($method, 0, 5)) == 'where') { + $name = Str::snake(substr($method, 5)); + array_unshift($args, $name); + return call_user_func_array([$this, 'where'], $args); + } elseif ($this->model && method_exists($this->model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $this); + + call_user_func_array([$this->model, $method], $args); + return $this; + } else { + throw new Exception('method not exist:' . static::class . '->' . $method); + } + } + + /** + * 创建一个新的查询对象 + * @access public + * @return BaseQuery + */ + public function newQuery(): BaseQuery + { + $query = new static($this->connection); + + if ($this->model) { + $query->model($this->model); + } + + if (isset($this->options['table'])) { + $query->table($this->options['table']); + } else { + $query->name($this->name); + } + + if (isset($this->options['json'])) { + $query->json($this->options['json'], $this->options['json_assoc']); + } + + if (isset($this->options['field_type'])) { + $query->setFieldType($this->options['field_type']); + } + + return $query; + } + + /** + * 获取当前的数据库Connection对象 + * @access public + * @return ConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 指定当前数据表名(不含前缀) + * @access public + * @param string $name 不含前缀的数据表名字 + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + return $this; + } + + /** + * 获取当前的数据表名称 + * @access public + * @return string + */ + public function getName(): string + { + return $this->name ?: $this->model->getName(); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $name 参数名称 + * @return mixed + */ + public function getConfig(string $name = '') + { + return $this->connection->getConfig($name); + } + + /** + * 得到当前或者指定名称的数据表 + * @access public + * @param string $name 不含前缀的数据表名字 + * @return mixed + */ + public function getTable(string $name = '') + { + if (empty($name) && isset($this->options['table'])) { + return $this->options['table']; + } + + $name = $name ?: $this->name; + + return $this->prefix . Str::snake($name); + } + + /** + * 设置字段类型信息 + * @access public + * @param array $type 字段类型信息 + * @return $this + */ + public function setFieldType(array $type) + { + $this->options['field_type'] = $type; + return $this; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->connection->getLastSql(); + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows(): int + { + return $this->connection->getNumRows(); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return mixed + */ + public function getLastInsID(string $sequence = null) + { + return $this->connection->getLastInsID($this, $sequence); + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(string $field, $default = null) + { + return $this->connection->value($this, $field, $default); + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(string $field, string $key = ''): array + { + return $this->connection->column($this, $field, $key); + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union UNION + * @param boolean $all 是否适用UNION ALL + * @return $this + */ + public function union($union, bool $all = false) + { + $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION'; + + if (is_array($union)) { + $this->options['union'] = array_merge($this->options['union'], $union); + } else { + $this->options['union'][] = $union; + } + + return $this; + } + + /** + * 查询SQL组装 union all + * @access public + * @param mixed $union UNION数据 + * @return $this + */ + public function unionAll($union) + { + return $this->union($union, true); + } + + /** + * 指定查询字段 + * @access public + * @param mixed $field 字段信息 + * @return $this + */ + public function field($field) + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Raw) { + $this->options['field'][] = $field; + return $this; + } + + if (is_string($field)) { + if (preg_match('/[\<\'\"\(]/', $field)) { + return $this->fieldRaw($field); + } + + $field = array_map('trim', explode(',', $field)); + } + + if (true === $field) { + // 获取全部字段 + $fields = $this->getTableFields(); + $field = $fields ?: ['*']; + } + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field); + + return $this; + } + + /** + * 指定要排除的查询字段 + * @access public + * @param array|string $field 要排除的字段 + * @return $this + */ + public function withoutField($field) + { + if (empty($field)) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + // 字段排除 + $fields = $this->getTableFields(); + $field = $fields ? array_diff($fields, $field) : $field; + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field); + + return $this; + } + + /** + * 指定其它数据表的查询字段 + * @access public + * @param mixed $field 字段信息 + * @param string $tableName 数据表名 + * @param string $prefix 字段前缀 + * @param string $alias 别名前缀 + * @return $this + */ + public function tableField($field, string $tableName, string $prefix = '', string $alias = '') + { + if (empty($field)) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + if (true === $field) { + // 获取全部字段 + $fields = $this->getTableFields($tableName); + $field = $fields ?: ['*']; + } + + // 添加统一的前缀 + $prefix = $prefix ?: $tableName; + foreach ($field as $key => &$val) { + if (is_numeric($key) && $alias) { + $field[$prefix . '.' . $val] = $alias . $val; + unset($field[$key]); + } elseif (is_numeric($key)) { + $val = $prefix . '.' . $val; + } + } + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field); + + return $this; + } + + /** + * 设置数据 + * @access public + * @param array $data 数据 + * @return $this + */ + public function data(array $data) + { + $this->options['data'] = $data; + + return $this; + } + + /** + * 去除查询参数 + * @access public + * @param string $option 参数名 留空去除所有参数 + * @return $this + */ + public function removeOption(string $option = '') + { + if ('' === $option) { + $this->options = []; + $this->bind = []; + } elseif (isset($this->options[$option])) { + unset($this->options[$option]); + } + + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param int $offset 起始位置 + * @param int $length 查询数量 + * @return $this + */ + public function limit(int $offset, int $length = null) + { + $this->options['limit'] = $offset . ($length ? ',' . $length : ''); + + return $this; + } + + /** + * 指定分页 + * @access public + * @param int $page 页数 + * @param int $listRows 每页数量 + * @return $this + */ + public function page(int $page, int $listRows = null) + { + $this->options['page'] = [$page, $listRows]; + + return $this; + } + + /** + * 指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function table($table) + { + if (is_string($table)) { + if (strpos($table, ')')) { + // 子查询 + } elseif (false === strpos($table, ',')) { + if (strpos($table, ' ')) { + [$item, $alias] = explode(' ', $table); + $table = []; + $this->alias([$item => $alias]); + $table[$item] = $alias; + } + } else { + $tables = explode(',', $table); + $table = []; + + foreach ($tables as $item) { + $item = trim($item); + if (strpos($item, ' ')) { + [$item, $alias] = explode(' ', $item); + $this->alias([$item => $alias]); + $table[$item] = $alias; + } else { + $table[] = $item; + } + } + } + } elseif (is_array($table)) { + $tables = $table; + $table = []; + + foreach ($tables as $key => $val) { + if (is_numeric($key)) { + $table[] = $val; + } else { + $this->alias([$key => $val]); + $table[$key] = $val; + } + } + } + + $this->options['table'] = $table; + + return $this; + } + + /** + * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) + * @access public + * @param string|array|Raw $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order($field, string $order = '') + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Raw) { + $this->options['order'][] = $field; + return $this; + } + + if (is_string($field)) { + if (!empty($this->options['via'])) { + $field = $this->options['via'] . '.' . $field; + } + if (strpos($field, ',')) { + $field = array_map('trim', explode(',', $field)); + } else { + $field = empty($order) ? $field : [$field => $order]; + } + } elseif (!empty($this->options['via'])) { + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $field[$key] = $this->options['via'] . '.' . $val; + } else { + $field[$this->options['via'] . '.' . $key] = $val; + unset($field[$key]); + } + } + } + + if (!isset($this->options['order'])) { + $this->options['order'] = []; + } + + if (is_array($field)) { + $this->options['order'] = array_merge($this->options['order'], $field); + } else { + $this->options['order'][] = $field; + } + + return $this; + } + + /** + * 分页查询 + * @access public + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @return Paginator + * @throws Exception + */ + public function paginate($listRows = null, $simple = false): Paginator + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + + $defaultConfig = [ + 'query' => [], //url额外参数 + 'fragment' => '', //url锚点 + 'var_page' => 'page', //分页变量 + 'list_rows' => 15, //每页数量 + ]; + + if (is_array($listRows)) { + $config = array_merge($defaultConfig, $listRows); + $listRows = intval($config['list_rows']); + } else { + $config = $defaultConfig; + $listRows = intval($listRows ?: $config['list_rows']); + } + + $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = $config['path'] ?? Paginator::getCurrentPath(); + + if (!isset($total) && !$simple) { + $options = $this->getOptions(); + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + + $bind = $this->bind; + $total = $this->count(); + $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); + } elseif ($simple) { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $total = null; + } else { + $results = $this->page($page, $listRows)->select(); + } + + $this->removeOption('limit'); + $this->removeOption('page'); + + return Paginator::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 根据数字类型字段进行分页查询(大数据) + * @access public + * @param int|array $listRows 每页数量或者分页配置 + * @param string $key 分页索引键 + * @param string $sort 索引键排序 asc|desc + * @return Paginator + * @throws Exception + */ + public function paginateX($listRows = null, string $key = null, string $sort = null): Paginator + { + $defaultConfig = [ + 'query' => [], //url额外参数 + 'fragment' => '', //url锚点 + 'var_page' => 'page', //分页变量 + 'list_rows' => 15, //每页数量 + ]; + + $config = is_array($listRows) ? array_merge($defaultConfig, $listRows) : $defaultConfig; + $listRows = is_int($listRows) ? $listRows : (int) $config['list_rows']; + $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']); + $page = $page < 1 ? 1 : $page; + + $config['path'] = $config['path'] ?? Paginator::getCurrentPath(); + + $key = $key ?: $this->getPk(); + $options = $this->getOptions(); + + if (is_null($sort)) { + $order = $options['order'] ?? ''; + if (!empty($order)) { + $sort = $order[$key] ?? 'desc'; + } else { + $this->order($key, 'desc'); + $sort = 'desc'; + } + } else { + $this->order($key, $sort); + } + + $newOption = $options; + unset($newOption['field'], $newOption['page']); + + $data = $this->newQuery() + ->options($newOption) + ->field($key) + ->where(true) + ->order($key, $sort) + ->limit(1) + ->find(); + + $result = $data[$key]; + + if (is_numeric($result)) { + $lastId = 'asc' == $sort ? ($result - 1) + ($page - 1) * $listRows : ($result + 1) - ($page - 1) * $listRows; + } else { + throw new Exception('not support type'); + } + + $results = $this->when($lastId, function ($query) use ($key, $sort, $lastId) { + $query->where($key, 'asc' == $sort ? '>' : '<', $lastId); + }) + ->limit($listRows) + ->select(); + + $this->options($options); + + return Paginator::make($results, $listRows, $page, null, true, $config); + } + + /** + * 根据最后ID查询更多N个数据 + * @access public + * @param int $limit LIMIT + * @param int|string $lastId LastId + * @param string $key 分页索引键 默认为主键 + * @param string $sort 索引键排序 asc|desc + * @return array + * @throws Exception + */ + public function more(int $limit, $lastId = null, string $key = null, string $sort = null): array + { + $key = $key ?: $this->getPk(); + + if (is_null($sort)) { + $order = $this->getOptions('order'); + if (!empty($order)) { + $sort = $order[$key] ?? 'desc'; + } else { + $this->order($key, 'desc'); + $sort = 'desc'; + } + } else { + $this->order($key, $sort); + } + + $result = $this->when($lastId, function ($query) use ($key, $sort, $lastId) { + $query->where($key, 'asc' == $sort ? '>' : '<', $lastId); + })->limit($limit)->select(); + + $last = $result->last(); + + $result->first(); + + return [ + 'data' => $result, + 'lastId' => $last[$key], + ]; + } + + /** + * 查询缓存 + * @access public + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string|array $tag 缓存标签 + * @return $this + */ + public function cache($key = true, $expire = null, $tag = null) + { + if (false === $key || !$this->getConnection()->getCache()) { + return $this; + } + + if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + $this->options['cache'] = [$key, $expire, $tag]; + + return $this; + } + + /** + * 指定查询lock + * @access public + * @param bool|string $lock 是否lock + * @return $this + */ + public function lock($lock = false) + { + $this->options['lock'] = $lock; + + if ($lock) { + $this->options['master'] = true; + } + + return $this; + } + + /** + * 指定数据表别名 + * @access public + * @param array|string $alias 数据表别名 + * @return $this + */ + public function alias($alias) + { + if (is_array($alias)) { + $this->options['alias'] = $alias; + } else { + $table = $this->getTable(); + + $this->options['alias'][$table] = $alias; + } + + return $this; + } + + /** + * 设置从主服务器读取数据 + * @access public + * @param bool $readMaster 是否从主服务器读取 + * @return $this + */ + public function master(bool $readMaster = true) + { + $this->options['master'] = $readMaster; + return $this; + } + + /** + * 设置是否严格检查字段名 + * @access public + * @param bool $strict 是否严格检查字段 + * @return $this + */ + public function strict(bool $strict = true) + { + $this->options['strict'] = $strict; + return $this; + } + + /** + * 设置JSON字段信息 + * @access public + * @param array $json JSON字段 + * @param bool $assoc 是否取出数组 + * @return $this + */ + public function json(array $json = [], bool $assoc = false) + { + $this->options['json'] = $json; + $this->options['json_assoc'] = $assoc; + return $this; + } + + /** + * 指定数据表主键 + * @access public + * @param string|array $pk 主键 + * @return $this + */ + public function pk($pk) + { + $this->pk = $pk; + return $this; + } + + /** + * 查询参数批量赋值 + * @access protected + * @param array $options 表达式参数 + * @return $this + */ + protected function options(array $options) + { + $this->options = $options; + return $this; + } + + /** + * 获取当前的查询参数 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getOptions(string $name = '') + { + if ('' === $name) { + return $this->options; + } + + return $this->options[$name] ?? null; + } + + /** + * 设置当前的查询参数 + * @access public + * @param string $option 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function setOption(string $option, $value) + { + $this->options[$option] = $value; + return $this; + } + + /** + * 设置当前字段添加的表别名 + * @access public + * @param string $via 临时表别名 + * @return $this + */ + public function via(string $via = '') + { + $this->options['via'] = $via; + + return $this; + } + + /** + * 保存记录 自动判断insert或者update + * @access public + * @param array $data 数据 + * @param bool $forceInsert 是否强制insert + * @return integer + */ + public function save(array $data = [], bool $forceInsert = false) + { + if ($forceInsert) { + return $this->insert($data); + } + + $this->options['data'] = array_merge($this->options['data'] ?? [], $data); + + if (!empty($this->options['where'])) { + $isUpdate = true; + } else { + $isUpdate = $this->parseUpdateData($this->options['data']); + } + + return $isUpdate ? $this->update() : $this->insert(); + } + + /** + * 插入记录 + * @access public + * @param array $data 数据 + * @param boolean $getLastInsID 返回自增主键 + * @return integer|string + */ + public function insert(array $data = [], bool $getLastInsID = false) + { + if (!empty($data)) { + $this->options['data'] = $data; + } + + return $this->connection->insert($this, $getLastInsID); + } + + /** + * 插入记录并获取自增ID + * @access public + * @param array $data 数据 + * @return integer|string + */ + public function insertGetId(array $data) + { + return $this->insert($data, true); + } + + /** + * 批量插入记录 + * @access public + * @param array $dataSet 数据集 + * @param integer $limit 每次写入数据限制 + * @return integer + */ + public function insertAll(array $dataSet = [], int $limit = 0): int + { + if (empty($dataSet)) { + $dataSet = $this->options['data'] ?? []; + } + + if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) { + $limit = (int) $this->options['limit']; + } + + return $this->connection->insertAll($this, $dataSet, $limit); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param array $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer + */ + public function selectInsert(array $fields, string $table): int + { + return $this->connection->selectInsert($this, $fields, $table); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return integer + * @throws Exception + */ + public function update(array $data = []): int + { + if (!empty($data)) { + $this->options['data'] = array_merge($this->options['data'] ?? [], $data); + } + + if (empty($this->options['where'])) { + $this->parseUpdateData($this->options['data']); + } + + if (empty($this->options['where']) && $this->model) { + $this->where($this->model->getWhere()); + } + + if (empty($this->options['where'])) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } + + return $this->connection->update($this); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + */ + public function delete($data = null): int + { + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + + if (empty($this->options['where']) && $this->model) { + $this->where($this->model->getWhere()); + } + + if (true !== $data && empty($this->options['where'])) { + // 如果条件为空 不进行删除操作 除非设置 1=1 + throw new Exception('delete without condition'); + } + + if (!empty($this->options['soft_delete'])) { + // 软删除 + list($field, $condition) = $this->options['soft_delete']; + if ($condition) { + unset($this->options['soft_delete']); + $this->options['data'] = [$field => $condition]; + + return $this->connection->update($this); + } + } + + $this->options['data'] = $data; + + return $this->connection->delete($this); + } + + /** + * 查找记录 + * @access public + * @param mixed $data 数据 + * @return Collection + * @throws Exception + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select($data = null): Collection + { + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $resultSet = $this->connection->select($this); + + // 返回结果处理 + if (!empty($this->options['fail']) && count($resultSet) == 0) { + $this->throwNotFound(); + } + + // 数据列表读取后的处理 + if (!empty($this->model)) { + // 生成模型对象 + $resultSet = $this->resultSetToModelCollection($resultSet); + } else { + $this->resultSet($resultSet); + } + + return $resultSet; + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 查询数据 + * @return array|Model|null + * @throws Exception + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find($data = null) + { + if (!is_null($data)) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + + if (empty($this->options['where']) && empty($this->options['order'])) { + $result = []; + } else { + $result = $this->connection->find($this); + } + + // 数据处理 + if (empty($result)) { + return $this->resultToEmpty(); + } + + if (!empty($this->model)) { + // 返回模型对象 + $this->resultToModel($result, $this->options); + } else { + $this->result($result); + } + + return $result; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access public + * @return array + */ + public function parseOptions(): array + { + $options = $this->getOptions(); + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + if (!isset($options['where'])) { + $options['where'] = []; + } elseif (isset($options['view'])) { + // 视图查询条件处理 + $this->parseView($options); + } + + if (!isset($options['field'])) { + $options['field'] = '*'; + } + + foreach (['data', 'order', 'join', 'union'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } + } + + if (!isset($options['strict'])) { + $options['strict'] = $this->connection->getConfig('fields_strict'); + } + + foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) { + if (!isset($options[$name])) { + $options[$name] = ''; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + [$page, $listRows] = $options['page']; + $page = $page > 0 ? $page : 1; + $listRows = $listRows ?: (is_numeric($options['limit']) ? $options['limit'] : 20); + $offset = $listRows * ($page - 1); + $options['limit'] = $offset . ',' . $listRows; + } + + $this->options = $options; + + return $options; + } + + /** + * 分析数据是否存在更新条件 + * @access public + * @param array $data 数据 + * @return bool + * @throws Exception + */ + public function parseUpdateData(&$data): bool + { + $pk = $this->getPk(); + $isUpdate = false; + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $this->where($pk, '=', $data[$pk]); + $this->options['key'] = $data[$pk]; + unset($data[$pk]); + $isUpdate = true; + } elseif (is_array($pk)) { + foreach ($pk as $field) { + if (isset($data[$field])) { + $this->where($field, '=', $data[$field]); + $isUpdate = true; + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + + return $isUpdate; + } + + /** + * 把主键值转换为查询条件 支持复合主键 + * @access public + * @param array|string $data 主键数据 + * @return void + * @throws Exception + */ + public function parsePkWhere($data): void + { + $pk = $this->getPk(); + + if (is_string($pk)) { + // 获取数据表 + if (empty($this->options['table'])) { + $this->options['table'] = $this->getTable(); + } + + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + + if (!empty($this->options['alias'][$table])) { + $alias = $this->options['alias'][$table]; + } + + $key = isset($alias) ? $alias . '.' . $pk : $pk; + // 根据主键查询 + if (is_array($data)) { + $this->where($key, 'in', $data); + } else { + $this->where($key, '=', $data); + $this->options['key'] = $data; + } + } + } + + /** + * 获取模型的更新条件 + * @access protected + * @param array $options 查询参数 + */ + protected function getModelUpdateCondition(array $options) + { + return $options['where']['AND'] ?? null; + } +} diff --git a/vendor/topthink/think-orm/src/db/Builder.php b/vendor/topthink/think-orm/src/db/Builder.php new file mode 100644 index 0000000..1b01c98 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Builder.php @@ -0,0 +1,1281 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Closure; +use PDO; +use think\db\exception\DbException as Exception; + +/** + * Db Builder + */ +abstract class Builder +{ + /** + * Connection对象 + * @var ConnectionInterface + */ + protected $connection; + + /** + * 查询表达式映射 + * @var array + */ + protected $exp = ['NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME']; + + /** + * 查询表达式解析 + * @var array + */ + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + ]; + + /** + * SELECT SQL表达式 + * @var string + */ + protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * UPDATE SQL表达式 + * @var string + */ + protected $updateSql = 'UPDATE%EXTRA% %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * DELETE SQL表达式 + * @var string + */ + protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 架构函数 + * @access public + * @param ConnectionInterface $connection 数据库连接对象实例 + */ + public function __construct(ConnectionInterface $connection) + { + $this->connection = $connection; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return ConnectionInterface + */ + public function getConnection(): ConnectionInterface + { + return $this->connection; + } + + /** + * 注册查询表达式解析 + * @access public + * @param string $name 解析方法 + * @param array $parser 匹配表达式数据 + * @return $this + */ + public function bindParser(string $name, array $parser) + { + $this->parser[$name] = $parser; + return $this; + } + + /** + * 数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @param array $fields 字段信息 + * @param array $bind 参数绑定 + * @return array + */ + protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array + { + if (empty($data)) { + return []; + } + + $options = $query->getOptions(); + + // 获取绑定信息 + if (empty($bind)) { + $bind = $query->getFieldsBindType(); + } + + if (empty($fields)) { + if ('*' == $options['field']) { + $fields = array_keys($bind); + } else { + $fields = $options['field']; + } + } + + $result = []; + + foreach ($data as $key => $val) { + $item = $this->parseKey($query, $key, true); + + if ($val instanceof Raw) { + $result[$item] = $val->getValue(); + continue; + } elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $query->getFieldType($key))) { + $val = json_encode($val); + } + + if (false !== strpos($key, '->')) { + [$key, $name] = explode('->', $key, 2); + $item = $this->parseKey($query, $key); + $result[$item] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key . '->' . $name, $val, $bind) . ')'; + } elseif (false === strpos($key, '.') && !in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + } elseif (is_null($val)) { + $result[$item] = 'NULL'; + } elseif (is_array($val) && !empty($val)) { + switch (strtoupper($val[0])) { + case 'INC': + $result[$item] = $item . ' + ' . floatval($val[1]); + break; + case 'DEC': + $result[$item] = $item . ' - ' . floatval($val[1]); + break; + } + } elseif (is_scalar($val)) { + // 过滤非标量数据 + $result[$item] = $this->parseDataBind($query, $key, $val, $bind); + } + } + + return $result; + } + + /** + * 数据绑定处理 + * @access protected + * @param Query $query 查询对象 + * @param string $key 字段名 + * @param mixed $data 数据 + * @param array $bind 绑定数据 + * @return string + */ + protected function parseDataBind(Query $query, string $key, $data, array $bind = []): string + { + if ($data instanceof Raw) { + return $data->getValue(); + } + + $name = $query->bindValue($data, $bind[$key] ?? PDO::PARAM_STR); + + return ':' . $name; + } + + /** + * 字段名分析 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + return $key; + } + + /** + * 查询额外参数分析 + * @access protected + * @param Query $query 查询对象 + * @param string $extra 额外参数 + * @return string + */ + protected function parseExtra(Query $query, string $extra): string + { + return preg_match('/^[\w]+$/i', $extra) ? ' ' . strtoupper($extra) : ''; + } + + /** + * field分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $fields 字段名 + * @return string + */ + protected function parseField(Query $query, $fields): string + { + if (is_array($fields)) { + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = []; + + foreach ($fields as $key => $field) { + if ($field instanceof Raw) { + $array[] = $field->getValue(); + } elseif (!is_numeric($key)) { + $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true); + } else { + $array[] = $this->parseKey($query, $field); + } + } + + $fieldsStr = implode(',', $array); + } else { + $fieldsStr = '*'; + } + + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $tables 表名 + * @return string + */ + protected function parseTable(Query $query, $tables): string + { + $item = []; + $options = $query->getOptions(); + + foreach ((array) $tables as $key => $table) { + if ($table instanceof Raw) { + $item[] = $table->getValue(); + } elseif (!is_numeric($key)) { + $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table); + } elseif (isset($options['alias'][$table])) { + $item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]); + } else { + $item[] = $this->parseKey($query, $table); + } + } + + return implode(',', $item); + } + + /** + * where分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $where 查询条件 + * @return string + */ + protected function parseWhere(Query $query, array $where): string + { + $options = $query->getOptions(); + $whereStr = $this->buildWhere($query, $where); + + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + [$field, $condition] = $options['soft_delete']; + + $binds = $query->getFieldsBindType(); + $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; + $whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, $binds); + } + + return empty($whereStr) ? '' : ' WHERE ' . $whereStr; + } + + /** + * 生成查询条件SQL + * @access public + * @param Query $query 查询对象 + * @param mixed $where 查询条件 + * @return string + */ + public function buildWhere(Query $query, array $where): string + { + if (empty($where)) { + $where = []; + } + + $whereStr = ''; + + $binds = $query->getFieldsBindType(); + + foreach ($where as $logic => $val) { + $str = $this->parseWhereLogic($query, $logic, $val, $binds); + + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str); + } + + return $whereStr; + } + + /** + * 不同字段使用相同查询条件(AND) + * @access protected + * @param Query $query 查询对象 + * @param string $logic Logic + * @param array $val 查询条件 + * @param array $binds 参数绑定 + * @return array + */ + protected function parseWhereLogic(Query $query, string $logic, array $val, array $binds = []): array + { + $where = []; + foreach ($val as $value) { + if ($value instanceof Raw) { + $where[] = ' ' . $logic . ' ( ' . $value->getValue() . ' )'; + continue; + } + + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); + } + $field = array_shift($value); + } elseif (true === $value) { + $where[] = ' ' . $logic . ' 1 '; + continue; + } elseif (!($value instanceof Closure)) { + throw new Exception('where express error:' . var_export($value, true)); + } + + if ($value instanceof Closure) { + // 使用闭包查询 + $where[] = $this->parseClosureWhere($query, $value, $logic); + } elseif (is_array($field)) { + $where[] = $this->parseMultiWhereField($query, $value, $field, $logic, $binds); + } elseif ($field instanceof Raw) { + $where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds); + } elseif (strpos($field, '|')) { + $where[] = $this->parseFieldsOr($query, $value, $field, $logic, $binds); + } elseif (strpos($field, '&')) { + $where[] = $this->parseFieldsAnd($query, $value, $field, $logic, $binds); + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds); + } + } + + return $where; + } + + /** + * 不同字段使用相同查询条件(AND) + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 查询条件 + * @param string $field 查询字段 + * @param string $logic Logic + * @param array $binds 参数绑定 + * @return string + */ + protected function parseFieldsAnd(Query $query, $value, string $field, string $logic, array $binds): string + { + $item = []; + + foreach (explode('&', $field) as $k) { + $item[] = $this->parseWhereItem($query, $k, $value, $binds); + } + + return ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )'; + } + + /** + * 不同字段使用相同查询条件(OR) + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 查询条件 + * @param string $field 查询字段 + * @param string $logic Logic + * @param array $binds 参数绑定 + * @return string + */ + protected function parseFieldsOr(Query $query, $value, string $field, string $logic, array $binds): string + { + $item = []; + + foreach (explode('|', $field) as $k) { + $item[] = $this->parseWhereItem($query, $k, $value, $binds); + } + + return ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )'; + } + + /** + * 闭包查询 + * @access protected + * @param Query $query 查询对象 + * @param Closure $value 查询条件 + * @param string $logic Logic + * @return string + */ + protected function parseClosureWhere(Query $query, Closure $value, string $logic): string + { + $newQuery = $query->newQuery(); + $value($newQuery); + $whereClosure = $this->buildWhere($newQuery, $newQuery->getOptions('where') ?: []); + + if (!empty($whereClosure)) { + $query->bind($newQuery->getBind(false)); + $where = ' ' . $logic . ' ( ' . $whereClosure . ' )'; + } + + return $where ?? ''; + } + + /** + * 复合条件查询 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 查询条件 + * @param mixed $field 查询字段 + * @param string $logic Logic + * @param array $binds 参数绑定 + * @return string + */ + protected function parseMultiWhereField(Query $query, $value, $field, string $logic, array $binds): string + { + array_unshift($value, $field); + + $where = []; + foreach ($value as $item) { + $where[] = $this->parseWhereItem($query, array_shift($item), $item, $binds); + } + + return ' ' . $logic . ' ( ' . implode(' AND ', $where) . ' )'; + } + + /** + * where子单元分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $field 查询字段 + * @param array $val 查询条件 + * @param array $binds 参数绑定 + * @return string + */ + protected function parseWhereItem(Query $query, $field, array $val, array $binds = []): string + { + // 字段分析 + $key = $field ? $this->parseKey($query, $field, true) : ''; + + list($exp, $value) = $val; + + // 检测操作符 + if (!is_string($exp)) { + throw new Exception('where express error:' . var_export($exp, true)); + } + + $exp = strtoupper($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } + + if (is_string($field) && 'LIKE' != $exp) { + $bindType = $binds[$field] ?? PDO::PARAM_STR; + } else { + $bindType = PDO::PARAM_STR; + } + + if ($value instanceof Raw) { + + } elseif (is_object($value) && method_exists($value, '__toString')) { + // 对象数据写入 + $value = $value->__toString(); + } + + if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + if (is_string($value) && 0 === strpos($value, ':') && $query->isBind(substr($value, 1))) { + } else { + $name = $query->bindValue($value, $bindType); + $value = ':' . $name; + } + } + + // 解析查询表达式 + foreach ($this->parser as $fun => $parse) { + if (in_array($exp, $parse)) { + return $this->$fun($query, $key, $exp, $value, $field, $bindType, $val[2] ?? 'AND'); + } + } + + throw new Exception('where express error:' . $exp); + } + + /** + * 模糊查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @param string $logic + * @return string + */ + protected function parseLike(Query $query, string $key, string $exp, $value, $field, int $bindType, string $logic): string + { + // 模糊匹配 + if (is_array($value)) { + $array = []; + foreach ($value as $item) { + $name = $query->bindValue($item, PDO::PARAM_STR); + $array[] = $key . ' ' . $exp . ' :' . $name; + } + + $whereStr = '(' . implode(' ' . strtoupper($logic) . ' ', $array) . ')'; + } else { + $whereStr = $key . ' ' . $exp . ' ' . $value; + } + + return $whereStr; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExp(Query $query, string $key, string $exp, Raw $value, string $field, int $bindType): string + { + // 表达式查询 + return '( ' . $key . ' ' . $value->getValue() . ' )'; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseColumn(Query $query, string $key, $exp, array $value, string $field, int $bindType): string + { + // 字段比较查询 + [$op, $field] = $value; + + if (!in_array(trim($op), ['=', '<>', '>', '>=', '<', '<='])) { + throw new Exception('where express error:' . var_export($value, true)); + } + + return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field, true) . ' )'; + } + + /** + * Null查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + // NULL 查询 + return $key . ' IS ' . $exp; + } + + /** + * 范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetween(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); + + $min = $query->bindValue($data[0], $bindType); + $max = $query->bindValue($data[1], $bindType); + + return $key . ' ' . $exp . ' :' . $min . ' AND :' . $max . ' '; + } + + /** + * Exists查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExists(Query $query, string $key, string $exp, $value, string $field, int $bindType): string + { + // EXISTS 查询 + if ($value instanceof Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Raw) { + $value = $value->getValue(); + } else { + throw new Exception('where express error:' . $value); + } + + return $exp . ' ( ' . $value . ' )'; + } + + /** + * 时间比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindType); + } + + /** + * 大小比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseCompare(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + if (is_array($value)) { + throw new Exception('where express error:' . $exp . var_export($value, true)); + } + + // 比较运算 + if ($value instanceof Closure) { + $value = $this->parseClosure($query, $value); + } + + if ('=' == $exp && is_null($value)) { + return $key . ' IS NULL'; + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * 时间范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetweenTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + if (is_string($value)) { + $value = explode(',', $value); + } + + return $key . ' ' . substr($exp, 0, -4) + . $this->parseDateTime($query, $value[0], $field, $bindType) + . ' AND ' + . $this->parseDateTime($query, $value[1], $field, $bindType); + + } + + /** + * IN查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseIn(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + // IN 查询 + if ($value instanceof Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Raw) { + $value = $value->getValue(); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', $value)); + $array = []; + + foreach ($value as $v) { + $name = $query->bindValue($v, $bindType); + $array[] = ':' . $name; + } + + if (count($array) == 1) { + return $key . ('IN' == $exp ? ' = ' : ' <> ') . $array[0]; + } else { + $zone = implode(',', $array); + $value = empty($zone) ? "''" : $zone; + } + } + + return $key . ' ' . $exp . ' (' . $value . ')'; + } + + /** + * 闭包子查询 + * @access protected + * @param Query $query 查询对象 + * @param \Closure $call + * @param bool $show + * @return string + */ + protected function parseClosure(Query $query, Closure $call, bool $show = true): string + { + $newQuery = $query->newQuery()->removeOption(); + $call($newQuery); + + return $newQuery->buildSql($show); + } + + /** + * 日期时间条件解析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value + * @param string $key + * @param integer $bindType + * @return string + */ + protected function parseDateTime(Query $query, $value, string $key, int $bindType): string + { + $options = $query->getOptions(); + + // 获取时间字段类型 + if (strpos($key, '.')) { + [$table, $key] = explode('.', $key); + + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { + $table = $pos; + } + } else { + $table = $options['table']; + } + + $type = $query->getFieldType($key); + + if ($type) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (is_int($value)) { + if (preg_match('/(datetime|timestamp)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + } + + $name = $query->bindValue($value, $bindType); + + return ':' . $name; + } + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, string $limit): string + { + return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; + } + + /** + * join分析 + * @access protected + * @param Query $query 查询对象 + * @param array $join + * @return string + */ + protected function parseJoin(Query $query, array $join): string + { + $joinStr = ''; + + foreach ($join as $item) { + [$table, $type, $on] = $item; + + if (strpos($on, '=')) { + [$val1, $val2] = explode('=', $on, 2); + + $condition = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2); + } else { + $condition = $on; + } + + $table = $this->parseTable($query, $table); + + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . $condition; + } + + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param Query $query 查询对象 + * @param array $order + * @return string + */ + protected function parseOrder(Query $query, array $order): string + { + $array = []; + foreach ($order as $key => $val) { + if ($val instanceof Raw) { + $array[] = $val->getValue(); + } elseif (is_array($val) && preg_match('/^[\w\.]+$/', $key)) { + $array[] = $this->parseOrderField($query, $key, $val); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } elseif (is_string($val)) { + if (is_numeric($key)) { + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + if (preg_match('/^[\w\.]+$/', $key)) { + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } else { + throw new Exception('order express error:' . $key); + } + } + } + + return empty($array) ? '' : ' ORDER BY ' . implode(',', $array); + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return ''; + } + + /** + * orderField分析 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param array $val + * @return string + */ + protected function parseOrderField(Query $query, string $key, array $val): string + { + if (isset($val['sort'])) { + $sort = $val['sort']; + unset($val['sort']); + } else { + $sort = ''; + } + + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $bind = $query->getFieldsBindType(); + + foreach ($val as $item) { + $val[] = $this->parseDataBind($query, $key, $item, $bind); + } + + return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort; + } + + /** + * group分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $group + * @return string + */ + protected function parseGroup(Query $query, $group): string + { + if (empty($group)) { + return ''; + } + + if (is_string($group)) { + $group = explode(',', $group); + } + + $val = []; + foreach ($group as $key) { + $val[] = $this->parseKey($query, $key); + } + + return ' GROUP BY ' . implode(',', $val); + } + + /** + * having分析 + * @access protected + * @param Query $query 查询对象 + * @param string $having + * @return string + */ + protected function parseHaving(Query $query, string $having): string + { + return !empty($having) ? ' HAVING ' . $having : ''; + } + + /** + * comment分析 + * @access protected + * @param Query $query 查询对象 + * @param string $comment + * @return string + */ + protected function parseComment(Query $query, string $comment): string + { + if (false !== strpos($comment, '*/')) { + $comment = strstr($comment, '*/', true); + } + + return !empty($comment) ? ' /* ' . $comment . ' */' : ''; + } + + /** + * distinct分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $distinct + * @return string + */ + protected function parseDistinct(Query $query, bool $distinct): string + { + return !empty($distinct) ? ' DISTINCT ' : ''; + } + + /** + * union分析 + * @access protected + * @param Query $query 查询对象 + * @param array $union + * @return string + */ + protected function parseUnion(Query $query, array $union): string + { + if (empty($union)) { + return ''; + } + + $type = $union['type']; + unset($union['type']); + + foreach ($union as $u) { + if ($u instanceof Closure) { + $sql[] = $type . ' ' . $this->parseClosure($query, $u); + } elseif (is_string($u)) { + $sql[] = $type . ' ( ' . $u . ' )'; + } + } + + return ' ' . implode(' ', $sql); + } + + /** + * index分析,可在操作链中指定需要强制使用的索引 + * @access protected + * @param Query $query 查询对象 + * @param mixed $index + * @return string + */ + protected function parseForce(Query $query, $index): string + { + if (empty($index)) { + return ''; + } + + if (is_array($index)) { + $index = join(',', $index); + } + + return sprintf(" FORCE INDEX ( %s ) ", $index); + } + + /** + * 设置锁机制 + * @access protected + * @param Query $query 查询对象 + * @param bool|string $lock + * @return string + */ + protected function parseLock(Query $query, $lock = false): string + { + if (is_bool($lock)) { + return $lock ? ' FOR UPDATE ' : ''; + } + + if (is_string($lock) && !empty($lock)) { + return ' ' . trim($lock) . ' '; + } else { + return ''; + } + } + + /** + * 生成查询SQL + * @access public + * @param Query $query 查询对象 + * @param bool $one 是否仅获取一个记录 + * @return string + */ + public function select(Query $query, bool $one = false): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($query, $options['table']), + $this->parseDistinct($query, $options['distinct']), + $this->parseExtra($query, $options['extra']), + $this->parseField($query, $options['field']), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseGroup($query, $options['group']), + $this->parseHaving($query, $options['having']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $one ? '1' : $options['limit']), + $this->parseUnion($query, $options['union']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + $this->parseForce($query, $options['force']), + ], + $this->selectSql); + } + + /** + * 生成Insert SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function insert(Query $query): string + { + $options = $query->getOptions(); + + // 分析并处理数据 + $data = $this->parseData($query, $options['data']); + if (empty($data)) { + return ''; + } + + $fields = array_keys($data); + $values = array_values($data); + + return str_replace( + ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertSql); + } + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @return string + */ + public function insertAll(Query $query, array $dataSet): string + { + $options = $query->getOptions(); + + // 获取绑定信息 + $bind = $query->getFieldsBindType(); + + // 获取合法的字段 + if ('*' == $options['field']) { + $allowFields = array_keys($bind); + } else { + $allowFields = $options['field']; + } + + $fields = []; + $values = []; + + foreach ($dataSet as $k => $data) { + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = 'SELECT ' . implode(',', array_values($data)); + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $fields), + implode(' UNION ALL ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); + } + + /** + * 生成slect insert SQL + * @access public + * @param Query $query 查询对象 + * @param array $fields 数据 + * @param string $table 数据表 + * @return string + */ + public function selectInsert(Query $query, array $fields, string $table): string + { + foreach ($fields as &$field) { + $field = $this->parseKey($query, $field, true); + } + + return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query); + } + + /** + * 生成update SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function update(Query $query): string + { + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + if (empty($data)) { + return ''; + } + + $set = []; + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%TABLE%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $set), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->updateSql); + } + + /** + * 生成delete SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function delete(Query $query): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->deleteSql); + } +} diff --git a/vendor/topthink/think-orm/src/db/CacheItem.php b/vendor/topthink/think-orm/src/db/CacheItem.php new file mode 100644 index 0000000..839f384 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/CacheItem.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use DateInterval; +use DateTime; +use DateTimeInterface; +use think\db\exception\InvalidArgumentException; + +/** + * CacheItem实现类 + */ +class CacheItem +{ + /** + * 缓存Key + * @var string + */ + protected $key; + + /** + * 缓存内容 + * @var mixed + */ + protected $value; + + /** + * 过期时间 + * @var int|DateTimeInterface + */ + protected $expire; + + /** + * 缓存tag + * @var string + */ + protected $tag; + + /** + * 缓存是否命中 + * @var bool + */ + protected $isHit = false; + + public function __construct(string $key = null) + { + $this->key = $key; + } + + /** + * 为此缓存项设置「键」 + * @access public + * @param string $key + * @return $this + */ + public function setKey(string $key) + { + $this->key = $key; + return $this; + } + + /** + * 返回当前缓存项的「键」 + * @access public + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * 返回当前缓存项的有效期 + * @access public + * @return DateTimeInterface|int|null + */ + public function getExpire() + { + if ($this->expire instanceof DateTimeInterface) { + return $this->expire; + } + + return $this->expire ? $this->expire - time() : null; + } + + /** + * 获取缓存Tag + * @access public + * @return string|array + */ + public function getTag() + { + return $this->tag; + } + + /** + * 凭借此缓存项的「键」从缓存系统里面取出缓存项 + * @access public + * @return mixed + */ + public function get() + { + return $this->value; + } + + /** + * 确认缓存项的检查是否命中 + * @access public + * @return bool + */ + public function isHit(): bool + { + return $this->isHit; + } + + /** + * 为此缓存项设置「值」 + * @access public + * @param mixed $value + * @return $this + */ + public function set($value) + { + $this->value = $value; + $this->isHit = true; + return $this; + } + + /** + * 为此缓存项设置所属标签 + * @access public + * @param string|array $tag + * @return $this + */ + public function tag($tag = null) + { + $this->tag = $tag; + return $this; + } + + /** + * 设置缓存项的有效期 + * @access public + * @param mixed $expire + * @return $this + */ + public function expire($expire) + { + if (is_null($expire)) { + $this->expire = null; + } elseif (is_numeric($expire) || $expire instanceof DateInterval) { + $this->expiresAfter($expire); + } elseif ($expire instanceof DateTimeInterface) { + $this->expire = $expire; + } else { + throw new InvalidArgumentException('not support datetime'); + } + + return $this; + } + + /** + * 设置缓存项的准确过期时间点 + * @access public + * @param DateTimeInterface $expiration + * @return $this + */ + public function expiresAt($expiration) + { + if ($expiration instanceof DateTimeInterface) { + $this->expire = $expiration; + } else { + throw new InvalidArgumentException('not support datetime'); + } + + return $this; + } + + /** + * 设置缓存项的过期时间 + * @access public + * @param int|DateInterval $timeInterval + * @return $this + * @throws InvalidArgumentException + */ + public function expiresAfter($timeInterval) + { + if ($timeInterval instanceof DateInterval) { + $this->expire = (int) DateTime::createFromFormat('U', (string) time())->add($timeInterval)->format('U'); + } elseif (is_numeric($timeInterval)) { + $this->expire = $timeInterval + time(); + } else { + throw new InvalidArgumentException('not support datetime'); + } + + return $this; + } + +} diff --git a/vendor/topthink/think-orm/src/db/Connection.php b/vendor/topthink/think-orm/src/db/Connection.php new file mode 100644 index 0000000..fb9f74b --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Connection.php @@ -0,0 +1,275 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Psr\SimpleCache\CacheInterface; +use think\DbManager; +use think\db\CacheItem; + +/** + * 数据库连接基础类 + */ +abstract class Connection +{ + + /** + * 当前SQL指令 + * @var string + */ + protected $queryStr = ''; + + /** + * 返回或者影响记录数 + * @var int + */ + protected $numRows = 0; + + /** + * 事务指令数 + * @var int + */ + protected $transTimes = 0; + + /** + * 错误信息 + * @var string + */ + protected $error = ''; + + /** + * 数据库连接ID 支持多个连接 + * @var array + */ + protected $links = []; + + /** + * 当前连接ID + * @var object + */ + protected $linkID; + + /** + * 当前读连接ID + * @var object + */ + protected $linkRead; + + /** + * 当前写连接ID + * @var object + */ + protected $linkWrite; + + /** + * 数据表信息 + * @var array + */ + protected $info = []; + + /** + * 查询开始时间 + * @var float + */ + protected $queryStartTime; + + /** + * Builder对象 + * @var Builder + */ + protected $builder; + + /** + * Db对象 + * @var Db + */ + protected $db; + + /** + * 是否读取主库 + * @var bool + */ + protected $readMaster = false; + + /** + * 数据库连接参数配置 + * @var array + */ + protected $config = []; + + /** + * 缓存对象 + * @var Cache + */ + protected $cache; + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * 设置当前的数据库Db对象 + * @access public + * @param DbManager $db + * @return void + */ + public function setDb(DbManager $db) + { + $this->db = $db; + } + + /** + * 设置当前的缓存对象 + * @access public + * @param CacheInterface $cache + * @return void + */ + public function setCache(CacheInterface $cache) + { + $this->cache = $cache; + } + + /** + * 获取当前的缓存对象 + * @access public + * @return CacheInterface|null + */ + public function getCache() + { + return $this->cache; + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig(string $config = '') + { + if ('' === $config) { + return $this->config; + } + + return $this->config[$config] ?? null; + } + + /** + * 数据库SQL监控 + * @access protected + * @param string $sql 执行的SQL语句 留空自动获取 + * @param bool $master 主从标记 + * @return void + */ + protected function trigger(string $sql = '', bool $master = false): void + { + $listen = $this->db->getListen(); + + if (!empty($listen)) { + $runtime = number_format((microtime(true) - $this->queryStartTime), 6); + $sql = $sql ?: $this->getLastsql(); + + if (empty($this->config['deploy'])) { + $master = null; + } + + foreach ($listen as $callback) { + if (is_callable($callback)) { + $callback($sql, $runtime, $master); + } + } + } + } + + /** + * 缓存数据 + * @access protected + * @param CacheItem $cacheItem 缓存Item + */ + protected function cacheData(CacheItem $cacheItem) + { + if ($cacheItem->getTag() && method_exists($this->cache, 'tag')) { + $this->cache->tag($cacheItem->getTag())->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire()); + } else { + $this->cache->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire()); + } + } + + /** + * 分析缓存Key + * @access protected + * @param BaseQuery $query 查询对象 + * @return string + */ + protected function getCacheKey(BaseQuery $query): string + { + if (!empty($query->getOptions('key'))) { + $key = 'think:' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $query->getOptions('key'); + } else { + $key = $query->getQueryGuid(); + } + + return $key; + } + + /** + * 分析缓存 + * @access protected + * @param BaseQuery $query 查询对象 + * @param array $cache 缓存信息 + * @return CacheItem + */ + protected function parseCache(BaseQuery $query, array $cache): CacheItem + { + [$key, $expire, $tag] = $cache; + + if ($key instanceof CacheItem) { + $cacheItem = $key; + } else { + if (true === $key) { + $key = $this->getCacheKey($query); + } + + $cacheItem = new CacheItem($key); + $cacheItem->expire($expire); + $cacheItem->tag($tag); + } + + return $cacheItem; + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows(): int + { + return $this->numRows; + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + // 关闭连接 + $this->close(); + } +} diff --git a/vendor/topthink/think-orm/src/db/ConnectionInterface.php b/vendor/topthink/think-orm/src/db/ConnectionInterface.php new file mode 100644 index 0000000..f6b1e08 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/ConnectionInterface.php @@ -0,0 +1,196 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Psr\SimpleCache\CacheInterface; +use think\DbManager; + +/** + * Connection interface + */ +interface ConnectionInterface +{ + /** + * 获取当前连接器类对应的Query类 + * @access public + * @return string + */ + public function getQueryClass(): string; + + /** + * 连接数据库方法 + * @access public + * @param array $config 接参数 + * @param integer $linkNum 连接序号 + * @return mixed + */ + public function connect(array $config = [], $linkNum = 0); + + /** + * 设置当前的数据库Db对象 + * @access public + * @param DbManager $db + * @return void + */ + public function setDb(DbManager $db); + + /** + * 设置当前的缓存对象 + * @access public + * @param CacheInterface $cache + * @return void + */ + public function setCache(CacheInterface $cache); + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig(string $config = ''); + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close(); + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find(BaseQuery $query): array; + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select(BaseQuery $query): array; + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + */ + public function insert(BaseQuery $query, bool $getLastInsID = false); + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param mixed $dataSet 数据集 + * @return integer + * @throws \Exception + * @throws \Throwable + */ + public function insertAll(BaseQuery $query, array $dataSet = []): int; + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return integer + * @throws Exception + * @throws PDOException + */ + public function update(BaseQuery $query): int; + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete(BaseQuery $query): int; + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $one 返回一个值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null); + + /** + * 得到某个列的数组 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $column 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, string $column, string $key = ''): array; + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transaction(callable $callback); + + /** + * 启动事务 + * @access public + * @return void + * @throws \PDOException + * @throws \Exception + */ + public function startTrans(); + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit(); + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback(); + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string; + +} diff --git a/vendor/topthink/think-orm/src/db/Fetch.php b/vendor/topthink/think-orm/src/db/Fetch.php new file mode 100644 index 0000000..76658e6 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Fetch.php @@ -0,0 +1,493 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use think\db\exception\DbException as Exception; +use think\helper\Str; + +/** + * SQL获取类 + */ +class Fetch +{ + /** + * 查询对象 + * @var Query + */ + protected $query; + + /** + * Connection对象 + * @var Connection + */ + protected $connection; + + /** + * Builder对象 + * @var Builder + */ + protected $builder; + + /** + * 创建一个查询SQL获取对象 + * + * @param Query $query 查询对象 + */ + public function __construct(Query $query) + { + $this->query = $query; + $this->connection = $query->getConnection(); + $this->builder = $this->connection->getBuilder(); + } + + /** + * 聚合查询 + * @access protected + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @return string + */ + protected function aggregate(string $aggregate, string $field): string + { + $this->query->parseOptions(); + + $field = $aggregate . '(' . $this->builder->parseKey($this->query, $field) . ') AS think_' . strtolower($aggregate); + + return $this->value($field, 0, false); + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return string + */ + public function value(string $field, $default = null, bool $one = true): string + { + $options = $this->query->parseOptions(); + + if (isset($options['field'])) { + $this->query->removeOption('field'); + } + + $this->query->setOption('field', (array) $field); + + // 生成查询SQL + $sql = $this->builder->select($this->query, $one); + + if (isset($options['field'])) { + $this->query->setOption('field', $options['field']); + } else { + $this->query->removeOption('field'); + } + + return $this->fetch($sql); + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return string + */ + public function column(string $field, string $key = ''): string + { + $options = $this->query->parseOptions(); + + if (isset($options['field'])) { + $this->query->removeOption('field'); + } + + if ($key && '*' != $field) { + $field = $key . ',' . $field; + } + + $field = array_map('trim', explode(',', $field)); + + $this->query->setOption('field', $field); + + // 生成查询SQL + $sql = $this->builder->select($this->query); + + if (isset($options['field'])) { + $this->query->setOption('field', $options['field']); + } else { + $this->query->removeOption('field'); + } + + return $this->fetch($sql); + } + + /** + * 插入记录 + * @access public + * @param array $data 数据 + * @return string + */ + public function insert(array $data = []): string + { + $options = $this->query->parseOptions(); + + if (!empty($data)) { + $this->query->setOption('data', $data); + } + + $sql = $this->builder->insert($this->query); + + return $this->fetch($sql); + } + + /** + * 插入记录并获取自增ID + * @access public + * @param array $data 数据 + * @return string + */ + public function insertGetId(array $data = []): string + { + return $this->insert($data); + } + + /** + * 保存数据 自动判断insert或者update + * @access public + * @param array $data 数据 + * @param bool $forceInsert 是否强制insert + * @return string + */ + public function save(array $data = [], bool $forceInsert = false): string + { + if ($forceInsert) { + return $this->insert($data); + } + + $data = array_merge($this->query->getOptions('data') ?: [], $data); + + $this->query->setOption('data', $data); + + if ($this->query->getOptions('where')) { + $isUpdate = true; + } else { + $isUpdate = $this->query->parseUpdateData($data); + } + + return $isUpdate ? $this->update() : $this->insert(); + } + + /** + * 批量插入记录 + * @access public + * @param array $dataSet 数据集 + * @param integer $limit 每次写入数据限制 + * @return string + */ + public function insertAll(array $dataSet = [], int $limit = null): string + { + $options = $this->query->parseOptions(); + + if (empty($dataSet)) { + $dataSet = $options['data']; + } + + if (empty($limit) && !empty($options['limit'])) { + $limit = $options['limit']; + } + + if ($limit) { + $array = array_chunk($dataSet, $limit, true); + $fetchSql = []; + foreach ($array as $item) { + $sql = $this->builder->insertAll($this->query, $item); + $bind = $this->query->getBind(); + + $fetchSql[] = $this->connection->getRealSql($sql, $bind); + } + + return implode(';', $fetchSql); + } + + $sql = $this->builder->insertAll($this->query, $dataSet); + + return $this->fetch($sql); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param array $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return string + */ + public function selectInsert(array $fields, string $table): string + { + $this->query->parseOptions(); + + $sql = $this->builder->selectInsert($this->query, $fields, $table); + + return $this->fetch($sql); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return string + */ + public function update(array $data = []): string + { + $options = $this->query->parseOptions(); + + $data = !empty($data) ? $data : $options['data']; + + $pk = $this->query->getPk(); + + if (empty($options['where'])) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $this->query->where($pk, '=', $data[$pk]); + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if (isset($data[$field])) { + $this->query->where($field, '=', $data[$field]); + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + + if (empty($this->query->getOptions('where'))) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } + } + + // 更新数据 + $this->query->setOption('data', $data); + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($this->query); + + return $this->fetch($sql); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return string + */ + public function delete($data = null): string + { + $options = $this->query->parseOptions(); + + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->query->parsePkWhere($data); + } + + if (!empty($options['soft_delete'])) { + // 软删除 + [$field, $condition] = $options['soft_delete']; + if ($condition) { + $this->query->setOption('soft_delete', null); + $this->query->setOption('data', [$field => $condition]); + // 生成删除SQL语句 + $sql = $this->builder->delete($this->query); + return $this->fetch($sql); + } + } + + // 生成删除SQL语句 + $sql = $this->builder->delete($this->query); + + return $this->fetch($sql); + } + + /** + * 查找记录 返回SQL + * @access public + * @param mixed $data + * @return string + */ + public function select($data = null): string + { + $this->query->parseOptions(); + + if (!is_null($data)) { + // 主键条件分析 + $this->query->parsePkWhere($data); + } + + // 生成查询SQL + $sql = $this->builder->select($this->query); + + return $this->fetch($sql); + } + + /** + * 查找单条记录 返回SQL语句 + * @access public + * @param mixed $data + * @return string + */ + public function find($data = null): string + { + $this->query->parseOptions(); + + if (!is_null($data)) { + // AR模式分析主键条件 + $this->query->parsePkWhere($data); + } + + // 生成查询SQL + $sql = $this->builder->select($this->query, true); + + // 获取实际执行的SQL语句 + return $this->fetch($sql); + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param mixed $data + * @return string + */ + public function selectOrFail($data = null): string + { + return $this->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param mixed $data + * @return string + */ + public function findOrFail($data = null): string + { + return $this->find($data); + } + + /** + * 查找单条记录 不存在返回空数据(或者空模型) + * @access public + * @param mixed $data 数据 + * @return string + */ + public function findOrEmpty($data = null) + { + return $this->find($data); + } + + /** + * 获取实际的SQL语句 + * @access public + * @param string $sql + * @return string + */ + public function fetch(string $sql): string + { + $bind = $this->query->getBind(); + + return $this->connection->getRealSql($sql, $bind); + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function count(string $field = '*'): string + { + $options = $this->query->parseOptions(); + + if (!empty($options['group'])) { + // 支持GROUP + $bind = $this->query->getBind(); + $subSql = $this->query->options($options)->field('count(' . $field . ') AS think_count')->bind($bind)->buildSql(); + + $query = $this->query->newQuery()->table([$subSql => '_group_count_']); + + return $query->fetchsql()->aggregate('COUNT', '*'); + } else { + return $this->aggregate('COUNT', $field); + } + } + + /** + * SUM查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function sum(string $field): string + { + return $this->aggregate('SUM', $field); + } + + /** + * MIN查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function min(string $field): string + { + return $this->aggregate('MIN', $field); + } + + /** + * MAX查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function max(string $field): string + { + return $this->aggregate('MAX', $field); + } + + /** + * AVG查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function avg(string $field): string + { + return $this->aggregate('AVG', $field); + } + + public function __call($method, $args) + { + if (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Str::snake(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Str::snake(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); + } + + $result = call_user_func_array([$this->query, $method], $args); + return $result === $this->query ? $this : $result; + } +} diff --git a/vendor/topthink/think-orm/src/db/Mongo.php b/vendor/topthink/think-orm/src/db/Mongo.php new file mode 100644 index 0000000..7d2e469 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Mongo.php @@ -0,0 +1,741 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\db; + +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\AuthenticationException; +use MongoDB\Driver\Exception\BulkWriteException; +use MongoDB\Driver\Exception\ConnectionException; +use MongoDB\Driver\Exception\InvalidArgumentException; +use MongoDB\Driver\Exception\RuntimeException; +use MongoDB\Driver\Query as MongoQuery; +use MongoDB\Driver\ReadPreference; +use MongoDB\Driver\WriteConcern; +use think\Collection; +use think\db\connector\Mongo as Connection; +use think\db\exception\DbException as Exception; +use think\Paginator; + +class Mongo extends BaseQuery +{ + /** + * 执行查询 返回数据集 + * @access public + * @param MongoQuery $query 查询对象 + * @return mixed + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function query(MongoQuery $query) + { + return $this->connection->query($this, $query); + } + + /** + * 执行指令 返回数据集 + * @access public + * @param Command $command 指令 + * @param string $dbName + * @param ReadPreference $readPreference readPreference + * @param string|array $typeMap 指定返回的typeMap + * @return mixed + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null) + { + return $this->connection->command($command, $dbName, $readPreference, $typeMap); + } + + /** + * 执行语句 + * @access public + * @param BulkWrite $bulk + * @return int + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function execute(BulkWrite $bulk) + { + return $this->connection->execute($this, $bulk); + } + + /** + * 执行command + * @access public + * @param string|array|object $command 指令 + * @param mixed $extra 额外参数 + * @param string $db 数据库名 + * @return array + */ + public function cmd($command, $extra = null, string $db = ''): array + { + $this->parseOptions(); + return $this->connection->cmd($this, $command, $extra, $db); + } + + /** + * 指定distinct查询 + * @access public + * @param string $field 字段名 + * @return array + */ + public function getDistinct(string $field) + { + $result = $this->cmd('distinct', $field); + return $result[0]['values']; + } + + /** + * 获取数据库的所有collection + * @access public + * @param string $db 数据库名称 留空为当前数据库 + * @throws Exception + */ + public function listCollections(string $db = '') + { + $cursor = $this->cmd('listCollections', null, $db); + $result = []; + foreach ($cursor as $collection) { + $result[] = $collection['name']; + } + + return $result; + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return integer + */ + public function count(string $field = null): int + { + $result = $this->cmd('count'); + + return $result[0]['n']; + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合指令 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate(string $aggregate, $field, bool $force = false) + { + $result = $this->cmd('aggregate', [strtolower($aggregate), $field]); + $value = $result[0]['aggregate'] ?? 0; + + if ($force) { + $value += 0; + } + + return $value; + } + + /** + * 多聚合操作 + * + * @param array $aggregate 聚合指令, 可以聚合多个参数, 如 ['sum' => 'field1', 'avg' => 'field2'] + * @param array $groupBy 类似mysql里面的group字段, 可以传入多个字段, 如 ['field_a', 'field_b', 'field_c'] + * @return array 查询结果 + */ + public function multiAggregate(array $aggregate, array $groupBy): array + { + $result = $this->cmd('multiAggregate', [$aggregate, $groupBy]); + + foreach ($result as &$row) { + if (isset($row['_id']) && !empty($row['_id'])) { + foreach ($row['_id'] as $k => $v) { + $row[$k] = $v; + } + unset($row['_id']); + } + } + + return $result; + } + + /** + * 字段值增长 + * @access public + * @param string $field 字段名 + * @param float $step 增长值 + * @return $this + */ + public function inc(string $field, float $step = 1) + { + $this->options['data'][$field] = ['$inc', $step]; + + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string $field 字段名 + * @param float $step 减少值 + * @return $this + */ + public function dec(string $field, float $step = 1) + { + return $this->inc($field, -1 * $step); + } + + /** + * 指定当前操作的Collection + * @access public + * @param string $table 表名 + * @return $this + */ + public function table($table) + { + $this->options['table'] = $table; + + return $this; + } + + /** + * table方法的别名 + * @access public + * @param string $collection + * @return $this + */ + public function collection(string $collection) + { + return $this->table($collection); + } + + /** + * 设置typeMap + * @access public + * @param string|array $typeMap + * @return $this + */ + public function typeMap($typeMap) + { + $this->options['typeMap'] = $typeMap; + return $this; + } + + /** + * awaitData + * @access public + * @param bool $awaitData + * @return $this + */ + public function awaitData(bool $awaitData) + { + $this->options['awaitData'] = $awaitData; + return $this; + } + + /** + * batchSize + * @access public + * @param integer $batchSize + * @return $this + */ + public function batchSize(int $batchSize) + { + $this->options['batchSize'] = $batchSize; + return $this; + } + + /** + * exhaust + * @access public + * @param bool $exhaust + * @return $this + */ + public function exhaust(bool $exhaust) + { + $this->options['exhaust'] = $exhaust; + return $this; + } + + /** + * 设置modifiers + * @access public + * @param array $modifiers + * @return $this + */ + public function modifiers(array $modifiers) + { + $this->options['modifiers'] = $modifiers; + return $this; + } + + /** + * 设置noCursorTimeout + * @access public + * @param bool $noCursorTimeout + * @return $this + */ + public function noCursorTimeout(bool $noCursorTimeout) + { + $this->options['noCursorTimeout'] = $noCursorTimeout; + return $this; + } + + /** + * 设置oplogReplay + * @access public + * @param bool $oplogReplay + * @return $this + */ + public function oplogReplay(bool $oplogReplay) + { + $this->options['oplogReplay'] = $oplogReplay; + return $this; + } + + /** + * 设置partial + * @access public + * @param bool $partial + * @return $this + */ + public function partial(bool $partial) + { + $this->options['partial'] = $partial; + return $this; + } + + /** + * maxTimeMS + * @access public + * @param string $maxTimeMS + * @return $this + */ + public function maxTimeMS(string $maxTimeMS) + { + $this->options['maxTimeMS'] = $maxTimeMS; + return $this; + } + + /** + * collation + * @access public + * @param array $collation + * @return $this + */ + public function collation(array $collation) + { + $this->options['collation'] = $collation; + return $this; + } + + /** + * 设置是否REPLACE + * @access public + * @param bool $replace 是否使用REPLACE写入数据 + * @return $this + */ + public function replace(bool $replace = true) + { + return $this; + } + + /** + * 设置返回字段 + * @access public + * @param mixed $field 字段信息 + * @return $this + */ + public function field($field) + { + if (empty($field) || '*' == $field) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $projection = []; + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $projection[$val] = 1; + } else { + $projection[$key] = $val; + } + } + + $this->options['projection'] = $projection; + + return $this; + } + + /** + * 指定要排除的查询字段 + * @access public + * @param array|string $field 要排除的字段 + * @return $this + */ + public function withoutField($field) + { + if (empty($field) || '*' == $field) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $projection = []; + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $projection[$val] = 0; + } else { + $projection[$key] = $val; + } + } + + $this->options['projection'] = $projection; + return $this; + } + + /** + * 设置skip + * @access public + * @param integer $skip + * @return $this + */ + public function skip(int $skip) + { + $this->options['skip'] = $skip; + return $this; + } + + /** + * 设置slaveOk + * @access public + * @param bool $slaveOk + * @return $this + */ + public function slaveOk(bool $slaveOk) + { + $this->options['slaveOk'] = $slaveOk; + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param int $offset 起始位置 + * @param int $length 查询数量 + * @return $this + */ + public function limit(int $offset, int $length = null) + { + if (is_null($length)) { + $length = $offset; + $offset = 0; + } + + $this->options['skip'] = $offset; + $this->options['limit'] = $length; + + return $this; + } + + /** + * 设置sort + * @access public + * @param array|string $field + * @param string $order + * @return $this + */ + public function order($field, string $order = '') + { + if (is_array($field)) { + $this->options['sort'] = $field; + } else { + $this->options['sort'][$field] = 'asc' == strtolower($order) ? 1 : -1; + } + return $this; + } + + /** + * 设置tailable + * @access public + * @param bool $tailable + * @return $this + */ + public function tailable(bool $tailable) + { + $this->options['tailable'] = $tailable; + return $this; + } + + /** + * 设置writeConcern对象 + * @access public + * @param WriteConcern $writeConcern + * @return $this + */ + public function writeConcern(WriteConcern $writeConcern) + { + $this->options['writeConcern'] = $writeConcern; + return $this; + } + + /** + * 获取当前数据表的主键 + * @access public + * @return string|array + */ + public function getPk() + { + return $this->pk ?: $this->connection->getConfig('pk'); + } + + /** + * 执行查询但只返回Cursor对象 + * @access public + * @return Cursor + */ + public function getCursor(): Cursor + { + $this->parseOptions(); + + return $this->connection->getCursor($this); + } + + /** + * 获取当前的查询标识 + * @access public + * @param mixed $data 要序列化的数据 + * @return string + */ + public function getQueryGuid($data = null): string + { + return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true))); + } + + /** + * 分页查询 + * @access public + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @return Paginator + * @throws Exception + */ + public function paginate($listRows = null, $simple = false): Paginator + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + + $defaultConfig = [ + 'query' => [], //url额外参数 + 'fragment' => '', //url锚点 + 'var_page' => 'page', //分页变量 + 'list_rows' => 15, //每页数量 + ]; + + if (is_array($listRows)) { + $config = array_merge($defaultConfig, $listRows); + $listRows = intval($config['list_rows']); + } else { + $config = $defaultConfig; + $listRows = intval($listRows ?: $config['list_rows']); + } + + $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = $config['path'] ?? Paginator::getCurrentPath(); + + if (!isset($total) && !$simple) { + $options = $this->getOptions(); + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + + $total = $this->count(); + $results = $this->options($options)->page($page, $listRows)->select(); + } elseif ($simple) { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $total = null; + } else { + $results = $this->page($page, $listRows)->select(); + } + + $this->removeOption('limit'); + $this->removeOption('page'); + + return Paginator::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string|array $column 分批处理的字段名 + * @param string $order 字段排序 + * @return bool + * @throws Exception + */ + public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool + { + $options = $this->getOptions(); + $column = $column ?: $this->getPk(); + + if (isset($options['order'])) { + unset($options['order']); + } + + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + $query = $this->options($options)->limit($count); + + if (strpos($column, '.')) { + [$alias, $key] = explode('.', $column); + } else { + $key = $column; + } + } + + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (isset($times)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = $resultSet->pop(); + $lastId = is_array($end) ? $end[$key] : $end->getData($key); + + $query = $this->options($options) + ->limit($count) + ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); + } + + $resultSet = $query->order($column, $order)->select(); + } + + return true; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access public + * @return array + */ + public function parseOptions(): array + { + $options = $this->options; + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + foreach (['where', 'data'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } + } + + $modifiers = empty($options['modifiers']) ? [] : $options['modifiers']; + if (isset($options['comment'])) { + $modifiers['$comment'] = $options['comment']; + } + + if (isset($options['maxTimeMS'])) { + $modifiers['$maxTimeMS'] = $options['maxTimeMS']; + } + + if (!empty($modifiers)) { + $options['modifiers'] = $modifiers; + } + + if (!isset($options['projection'])) { + $options['projection'] = []; + } + + if (!isset($options['typeMap'])) { + $options['typeMap'] = $this->getConfig('type_map'); + } + + if (!isset($options['limit'])) { + $options['limit'] = 0; + } + + foreach (['master', 'fetch_sql', 'fetch_cursor'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + 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['skip'] = intval($offset); + $options['limit'] = intval($listRows); + } + + $this->options = $options; + + return $options; + } + + /** + * 获取字段类型信息 + * @access public + * @return array + */ + public function getFieldsType(): array + { + if (!empty($this->options['field_type'])) { + return $this->options['field_type']; + } + + return []; + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getFieldType(string $field) + { + $fieldType = $this->getFieldsType(); + + return $fieldType[$field] ?? null; + } +} diff --git a/vendor/topthink/think-orm/src/db/PDOConnection.php b/vendor/topthink/think-orm/src/db/PDOConnection.php new file mode 100644 index 0000000..5cce2e1 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/PDOConnection.php @@ -0,0 +1,1697 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Closure; +use PDO; +use PDOStatement; +use think\db\exception\BindParamException; +use think\db\exception\DataNotFoundException; +use think\db\exception\ModelNotFoundException; +use think\db\exception\PDOException; + +/** + * 数据库连接基础类 + */ +abstract class PDOConnection extends Connection implements ConnectionInterface +{ + const PARAM_FLOAT = 21; + + /** + * 数据库连接参数配置 + * @var array + */ + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 模型写入后自动读取主服务器 + 'read_master' => false, + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 开启字段缓存 + 'fields_cache' => false, + // 监听SQL + 'trigger_sql' => true, + // Builder类 + 'builder' => '', + // Query类 + 'query' => '', + // 是否需要断线重连 + 'break_reconnect' => false, + // 断线标识字符串 + 'break_match_str' => [], + // 字段缓存路径 + 'schema_cache_path' => '', + ]; + + /** + * PDO操作实例 + * @var PDOStatement + */ + protected $PDOStatement; + + /** + * 当前SQL指令 + * @var string + */ + protected $queryStr = ''; + + /** + * 事务指令数 + * @var int + */ + protected $transTimes = 0; + + /** + * 重连次数 + * @var int + */ + protected $reConnectTimes = 0; + + /** + * 查询结果类型 + * @var int + */ + protected $fetchType = PDO::FETCH_ASSOC; + + /** + * 字段属性大小写 + * @var int + */ + protected $attrCase = PDO::CASE_LOWER; + + /** + * 数据表信息 + * @var array + */ + protected $info = []; + + /** + * 查询开始时间 + * @var float + */ + protected $queryStartTime; + + /** + * PDO连接参数 + * @var array + */ + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false, + ]; + + /** + * 参数绑定类型映射 + * @var array + */ + protected $bindType = [ + 'string' => PDO::PARAM_STR, + 'str' => PDO::PARAM_STR, + 'integer' => PDO::PARAM_INT, + 'int' => PDO::PARAM_INT, + 'boolean' => PDO::PARAM_BOOL, + 'bool' => PDO::PARAM_BOOL, + 'float' => self::PARAM_FLOAT, + 'datetime' => PDO::PARAM_STR, + 'timestamp' => PDO::PARAM_STR, + ]; + + /** + * 服务器断线标识字符 + * @var array + */ + protected $breakMatchStr = [ + 'server has gone away', + 'no connection to the server', + 'Lost connection', + 'is dead or not enabled', + 'Error while sending', + 'decryption failed or bad record mac', + 'server closed the connection unexpectedly', + 'SSL connection has been closed unexpectedly', + 'Error writing data to the connection', + 'Resource deadlock avoided', + 'failed with errno', + ]; + + /** + * 绑定参数 + * @var array + */ + protected $bind = []; + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + + // 创建Builder对象 + $class = $this->getBuilderClass(); + + $this->builder = new $class($this); + } + + /** + * 获取当前连接器类对应的Query类 + * @access public + * @return string + */ + public function getQueryClass(): string + { + return $this->getConfig('query') ?: Query::class; + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilderClass(): string + { + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + } + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + abstract protected function parseDsn(array $config); + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName 数据表名称 + * @return array + */ + abstract public function getFields(string $tableName); + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName 数据库名称 + * @return array + */ + abstract public function getTables(string $dbName); + + /** + * 对返数据表字段信息进行大小写转换出来 + * @access public + * @param array $info 字段信息 + * @return array + */ + public function fieldCase(array $info): array + { + // 字段大小写转换 + switch ($this->attrCase) { + case PDO::CASE_LOWER: + $info = array_change_key_case($info); + break; + case PDO::CASE_UPPER: + $info = array_change_key_case($info, CASE_UPPER); + break; + case PDO::CASE_NATURAL: + default: + // 不做转换 + } + + return $info; + } + + /** + * 获取字段类型 + * @access protected + * @param string $type 字段类型 + * @return string + */ + protected function getFieldType(string $type): string + { + if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $result = 'string'; + } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) { + $result = 'float'; + } elseif (preg_match('/(int|serial|bit)/is', $type)) { + $result = 'int'; + } elseif (preg_match('/bool/is', $type)) { + $result = 'bool'; + } elseif (0 === strpos($type, 'timestamp')) { + $result = 'timestamp'; + } elseif (0 === strpos($type, 'datetime')) { + $result = 'datetime'; + } else { + $result = 'string'; + } + + return $result; + } + + /** + * 获取字段绑定类型 + * @access public + * @param string $type 字段类型 + * @return integer + */ + public function getFieldBindType(string $type): int + { + if (in_array($type, ['integer', 'string', 'float', 'boolean', 'bool', 'int', 'str'])) { + $bind = $this->bindType[$type]; + } elseif (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $bind = PDO::PARAM_STR; + } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) { + $bind = self::PARAM_FLOAT; + } elseif (preg_match('/(int|serial|bit)/is', $type)) { + $bind = PDO::PARAM_INT; + } elseif (preg_match('/bool/is', $type)) { + $bind = PDO::PARAM_BOOL; + } else { + $bind = PDO::PARAM_STR; + } + + return $bind; + } + + /** + * 获取数据表信息 + * @access public + * @param mixed $tableName 数据表名 留空自动获取 + * @param string $fetch 获取信息类型 包括 fields type bind pk + * @return mixed + */ + public function getTableInfo($tableName, string $fetch = '') + { + if (is_array($tableName)) { + $tableName = key($tableName) ?: current($tableName); + } + + if (strpos($tableName, ',') || strpos($tableName, ')')) { + // 多表不获取字段信息 + return []; + } + + [$tableName] = explode(' ', $tableName); + + if (!strpos($tableName, '.')) { + $schema = $this->getConfig('database') . '.' . $tableName; + } else { + $schema = $tableName; + } + + if (!isset($this->info[$schema])) { + // 读取字段缓存 + $cacheFile = $this->config['schema_cache_path'] . $schema . '.php'; + + if ($this->config['fields_cache'] && is_file($cacheFile)) { + $info = include $cacheFile; + } else { + $info = $this->getTableFieldsInfo($tableName); + if ($this->config['fields_cache']) { + if (!is_dir($this->config['schema_cache_path'])) { + mkdir($this->config['schema_cache_path'], 0755, true); + } + + $content = ' $val) { + $bind[$name] = $this->getFieldBindType($val); + } + + $this->info[$schema] = [ + 'fields' => array_keys($info), + 'type' => $info, + 'bind' => $bind, + 'pk' => $pk, + 'autoinc' => $autoinc, + ]; + } + + return $fetch ? $this->info[$schema][$fetch] : $this->info[$schema]; + } + + /** + * 获取数据表的字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFieldsInfo(string $tableName): array + { + $fields = $this->getFields($tableName); + $info = []; + + foreach ($fields as $key => $val) { + // 记录字段类型 + $info[$key] = $this->getFieldType($val['type']); + + if (!empty($val['primary'])) { + $pk[] = $key; + } + + if (!empty($val['autoinc'])) { + $autoinc = $key; + } + } + + if (isset($pk)) { + // 设置主键 + $pk = count($pk) > 1 ? $pk : $pk[0]; + $info['_pk'] = $pk; + } + + if (isset($autoinc)) { + $info['_autoinc'] = $autoinc; + } + + return $info; + } + + /** + * 获取数据表的主键 + * @access public + * @param mixed $tableName 数据表名 + * @return string|array + */ + public function getPk($tableName) + { + return $this->getTableInfo($tableName, 'pk'); + } + + /** + * 获取数据表的自增主键 + * @access public + * @param mixed $tableName 数据表名 + * @return string + */ + public function getAutoInc($tableName) + { + return $this->getTableInfo($tableName, 'autoinc'); + } + + /** + * 获取数据表字段信息 + * @access public + * @param mixed $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName): array + { + return $this->getTableInfo($tableName, 'fields'); + } + + /** + * 获取数据表字段类型 + * @access public + * @param mixed $tableName 数据表名 + * @param string $field 字段名 + * @return array|string + */ + public function getFieldsType($tableName, string $field = null) + { + $result = $this->getTableInfo($tableName, 'type'); + + if ($field && isset($result[$field])) { + return $result[$field]; + } + + return $result; + } + + /** + * 获取数据表绑定信息 + * @access public + * @param mixed $tableName 数据表名 + * @return array + */ + public function getFieldsBind($tableName): array + { + return $this->getTableInfo($tableName, 'bind'); + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @return PDO + * @throws PDOException + */ + public function connect(array $config = [], $linkNum = 0, $autoConnection = false): PDO + { + if (isset($this->links[$linkNum])) { + return $this->links[$linkNum]; + } + + if (empty($config)) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + + // 连接参数 + if (isset($config['params']) && is_array($config['params'])) { + $params = $config['params'] + $this->params; + } else { + $params = $this->params; + } + + // 记录当前字段属性大小写设置 + $this->attrCase = $params[PDO::ATTR_CASE]; + + if (!empty($config['break_match_str'])) { + $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']); + } + + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + + $startTime = microtime(true); + + $this->links[$linkNum] = $this->createPdo($config['dsn'], $config['username'], $config['password'], $params); + + // SQL监控 + if (!empty($config['trigger_sql'])) { + $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); + } + + return $this->links[$linkNum]; + } catch (\PDOException $e) { + if ($autoConnection) { + $this->db->log($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; + } + } + } + + /** + * 创建PDO实例 + * @param $dsn + * @param $username + * @param $password + * @param $params + * @return PDO + */ + protected function createPdo($dsn, $username, $password, $params) + { + return new PDO($dsn, $username, $password, $params); + } + + /** + * 释放查询结果 + * @access public + */ + public function free(): void + { + $this->PDOStatement = null; + } + + /** + * 获取PDO对象 + * @access public + * @return \PDO|false + */ + public function getPdo() + { + if (!$this->linkID) { + return false; + } + + return $this->linkID; + } + + /** + * 执行查询 使用生成器返回数据 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param \think\Model $model 模型对象实例 + * @param array $condition 查询条件 + * @return \Generator + */ + public function getCursor(BaseQuery $query, string $sql, array $bind = [], $model = null, $condition = null) + { + $this->queryPDOStatement($query, $sql, $bind); + + // 返回结果集 + while ($result = $this->PDOStatement->fetch($this->fetchType)) { + if ($model) { + yield $model->newInstance($result, $condition); + } else { + yield $result; + } + } + } + + /** + * 执行查询 返回数据集 + * @access public + * @param BaseQuery $query 查询对象 + * @param mixed $sql sql指令 + * @param array $bind 参数绑定 + * @return array + * @throws BindParamException + * @throws \PDOException + * @throws \Exception + * @throws \Throwable + */ + public function query(BaseQuery $query, $sql, array $bind = []): array + { + // 分析查询表达式 + $query->parseOptions(); + + if ($query->getOptions('cache')) { + // 检查查询缓存 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + if ($sql instanceof Closure) { + $sql = $sql($query); + $bind = $query->getBind(); + } + + $master = $query->getOptions('master') ? true : false; + $procedure = $query->getOptions('procedure') ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + $this->getPDOStatement($sql, $bind, $master, $procedure); + + $resultSet = $this->getResult($procedure); + + if (isset($cacheItem) && $resultSet) { + // 缓存数据集 + $cacheItem->set($resultSet); + $this->cacheData($cacheItem); + } + + return $resultSet; + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @param BaseQuery $query 查询对象 + * @return \PDOStatement + */ + public function pdo(BaseQuery $query): PDOStatement + { + $bind = $query->getBind(); + // 生成查询SQL + $sql = $this->builder->select($query); + + return $this->queryPDOStatement($query, $sql, $bind); + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $procedure 是否为存储过程调用 + * @return PDOStatement + * @throws BindParamException + * @throws \PDOException + * @throws \Exception + * @throws \Throwable + */ + public function getPDOStatement(string $sql, array $bind = [], bool $master = false, bool $procedure = false): PDOStatement + { + $this->initConnect($this->readMaster ?: $master); + + // 记录SQL语句 + $this->queryStr = $sql; + + $this->bind = $bind; + + $this->db->updateQueryTimes(); + + try { + $this->queryStartTime = microtime(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + + // 执行查询 + $this->PDOStatement->execute(); + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger('', $master); + } + + $this->reConnectTimes = 0; + + return $this->PDOStatement; + } catch (\Throwable | \Exception $e) { + if ($this->reConnectTimes < 4 && $this->isBreak($e)) { + ++$this->reConnectTimes; + return $this->close()->getPDOStatement($sql, $bind, $master, $procedure); + } + + if ($e instanceof \PDOException) { + throw new PDOException($e, $this->config, $this->getLastsql()); + } else { + throw $e; + } + } + } + + /** + * 执行语句 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $origin 是否原生查询 + * @return int + * @throws BindParamException + * @throws \PDOException + * @throws \Exception + * @throws \Throwable + */ + public function execute(BaseQuery $query, string $sql, array $bind = [], bool $origin = false): int + { + if ($origin) { + $query->parseOptions(); + } + + $this->queryPDOStatement($query->master(true), $sql, $bind); + + if (!$origin && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { + $this->readMaster = true; + } + + $this->numRows = $this->PDOStatement->rowCount(); + + if ($query->getOptions('cache')) { + // 清理缓存数据 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + $tag = $cacheItem->getTag(); + + if (isset($key) && $this->cache->has($key)) { + $this->cache->delete($key); + } elseif (!empty($tag) && method_exists($this->cache, 'tag')) { + $this->cache->tag($tag)->clear(); + } + } + + return $this->numRows; + } + + protected function queryPDOStatement(BaseQuery $query, string $sql, array $bind = []): PDOStatement + { + $options = $query->getOptions(); + $master = !empty($options['master']) ? true : false; + $procedure = !empty($options['procedure']) ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + return $this->getPDOStatement($sql, $bind, $master, $procedure); + } + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find(BaseQuery $query): array + { + // 事件回调 + $result = $this->db->trigger('before_find', $query); + + if (!$result) { + // 执行查询 + $resultSet = $this->query($query, function ($query) { + return $this->builder->select($query, true); + }); + + $result = $resultSet[0] ?? []; + } + + return $result; + } + + /** + * 使用游标查询记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return \Generator + */ + public function cursor(BaseQuery $query) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $condition = $options['where']['AND'] ?? null; + + // 执行查询操作 + return $this->getCursor($query, $sql, $query->getBind(), $query->getModel(), $condition); + } + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select(BaseQuery $query): array + { + $resultSet = $this->db->trigger('before_select', $query); + + if (!$resultSet) { + // 执行查询操作 + $resultSet = $this->query($query, function ($query) { + return $this->builder->select($query); + }); + } + + return $resultSet; + } + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + */ + public function insert(BaseQuery $query, bool $getLastInsID = false) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成SQL语句 + $sql = $this->builder->insert($query); + + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($query, $sql, $query->getBind()); + + if ($result) { + $sequence = $options['sequence'] ?? null; + $lastInsId = $this->getLastInsID($query, $sequence); + + $data = $options['data']; + + if ($lastInsId) { + $pk = $query->getAutoInc(); + if ($pk) { + $data[$pk] = $lastInsId; + } + } + + $query->setOption('data', $data); + + $this->db->trigger('after_insert', $query); + + if ($getLastInsID && $lastInsId) { + return $lastInsId; + } + } + + return $result; + } + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param mixed $dataSet 数据集 + * @param integer $limit 每次写入数据限制 + * @return integer + * @throws \Exception + * @throws \Throwable + */ + public function insertAll(BaseQuery $query, array $dataSet = [], int $limit = 0): int + { + if (!is_array(reset($dataSet))) { + return 0; + } + + $query->parseOptions(); + + if (0 === $limit && count($dataSet) >= 5000) { + $limit = 1000; + } + + if ($limit) { + // 分批写入 自动启动事务支持 + $this->startTrans(); + + try { + $array = array_chunk($dataSet, $limit, true); + $count = 0; + + foreach ($array as $item) { + $sql = $this->builder->insertAll($query, $item); + $count += $this->execute($query, $sql, $query->getBind()); + } + + // 提交事务 + $this->commit(); + } catch (\Exception | \Throwable $e) { + $this->rollback(); + throw $e; + } + + return $count; + } + + $sql = $this->builder->insertAll($query, $dataSet); + + return $this->execute($query, $sql, $query->getBind()); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer + * @throws PDOException + */ + public function selectInsert(BaseQuery $query, array $fields, string $table): int + { + // 分析查询表达式 + $query->parseOptions(); + + $sql = $this->builder->selectInsert($query, $fields, $table); + + return $this->execute($query, $sql, $query->getBind()); + } + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return integer + * @throws PDOException + */ + public function update(BaseQuery $query): int + { + $query->parseOptions(); + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($query); + + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($query, $sql, $query->getBind()); + + if ($result) { + $this->db->trigger('after_update', $query); + } + + return $result; + } + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws PDOException + */ + public function delete(BaseQuery $query): int + { + // 分析查询表达式 + $query->parseOptions(); + + // 生成删除SQL语句 + $sql = $this->builder->delete($query); + + // 执行操作 + $result = $this->execute($query, $sql, $query->getBind()); + + if ($result) { + $this->db->trigger('after_delete', $query); + } + + return $result; + } + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $one 返回一个值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null, bool $one = true) + { + $options = $query->parseOptions(); + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (isset($options['group'])) { + $query->group(''); + } + + $query->setOption('field', (array) $field); + + if (!empty($options['cache'])) { + $cacheItem = $this->parseCache($query, $options['cache']); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + // 生成查询SQL + $sql = $this->builder->select($query, $one); + + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + if (isset($options['group'])) { + $query->setOption('group', $options['group']); + } + + // 执行查询操作 + $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']); + + $result = $pdo->fetchColumn(); + + if (isset($cacheItem)) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return false !== $result ? $result : $default; + } + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $aggregate 聚合方法 + * @param mixed $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate(BaseQuery $query, string $aggregate, $field, bool $force = false) + { + if (is_string($field) && 0 === stripos($field, 'DISTINCT ')) { + [$distinct, $field] = explode(' ', $field); + } + + $field = $aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $this->builder->parseKey($query, $field, true) . ') AS think_' . strtolower($aggregate); + + $result = $this->value($query, $field, 0, false); + + return $force ? (float) $result : $result; + } + + /** + * 得到某个列的数组 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $column 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, string $column, string $key = ''): array + { + $options = $query->parseOptions(); + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if ($key && '*' != $column) { + $field = $key . ',' . $column; + } else { + $field = $column; + } + + $field = array_map('trim', explode(',', $field)); + + $query->setOption('field', $field); + + if (!empty($options['cache'])) { + // 判断查询缓存 + $cacheItem = $this->parseCache($query, $options['cache']); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + // 生成查询SQL + $sql = $this->builder->select($query); + + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + // 执行查询操作 + $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']); + + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + + if (empty($resultSet)) { + $result = []; + } elseif (('*' == $column || strpos($column, ',')) && $key) { + $result = array_column($resultSet, null, $key); + } else { + $fields = array_keys($resultSet[0]); + $key = $key ?: array_shift($fields); + + if (strpos($column, ',')) { + $column = null; + } elseif (strpos($column, '.')) { + [$alias, $column] = explode('.', $column); + } + + if (strpos($key, '.')) { + [$alias, $key] = explode('.', $key); + } + + $result = array_column($resultSet, $column, $key); + } + + if (isset($cacheItem)) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return $result; + } + + /** + * 根据参数绑定组装最终的SQL语句 便于调试 + * @access public + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 + * @return string + */ + public function getRealSql(string $sql, array $bind = []): string + { + foreach ($bind as $key => $val) { + $value = is_array($val) ? $val[0] : $val; + $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + + if ((self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) && is_string($value)) { + $value = '\'' . addslashes($value) . '\''; + } elseif (PDO::PARAM_INT == $type && '' === $value) { + $value = 0; + } + + // 判断占位符 + $sql = is_numeric($key) ? + substr_replace($sql, $value, strpos($sql, '?'), 1) : + substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key)); + } + + return rtrim($sql); + } + + /** + * 参数绑定 + * 支持 ['name'=>'value','id'=>123] 对应命名占位符 + * 或者 ['value',123] 对应问号占位符 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindValue(array $bind = []): void + { + foreach ($bind as $key => $val) { + // 占位符 + $param = is_numeric($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { + $val[0] = 0; + } elseif (self::PARAM_FLOAT == $val[1]) { + $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0]; + $val[1] = PDO::PARAM_STR; + } + + $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + + if (!$result) { + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 存储过程的输入输出参数绑定 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindParam(array $bind): void + { + foreach ($bind as $key => $val) { + $param = is_numeric($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + array_unshift($val, $param); + $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + + if (!$result) { + $param = array_shift($val); + + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 获得数据集数组 + * @access protected + * @param bool $procedure 是否存储过程 + * @return array + */ + protected function getResult(bool $procedure = false): array + { + if ($procedure) { + // 存储过程返回结果 + return $this->procedure(); + } + + $result = $this->PDOStatement->fetchAll($this->fetchType); + + $this->numRows = count($result); + + return $result; + } + + /** + * 获得存储过程数据集 + * @access protected + * @return array + */ + protected function procedure(): array + { + $item = []; + + do { + $result = $this->getResult(); + if (!empty($result)) { + $item[] = $result; + } + } while ($this->PDOStatement->nextRowset()); + + $this->numRows = count($item); + + return $item; + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transaction(callable $callback) + { + $this->startTrans(); + + try { + $result = null; + if (is_callable($callback)) { + $result = $callback($this); + } + + $this->commit(); + return $result; + } catch (\Exception | \Throwable $e) { + $this->rollback(); + throw $e; + } + } + + /** + * 启动事务 + * @access public + * @return void + * @throws \PDOException + * @throws \Exception + */ + public function startTrans(): void + { + $this->initConnect(true); + + ++$this->transTimes; + + try { + if (1 == $this->transTimes) { + $this->linkID->beginTransaction(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepoint('trans' . $this->transTimes) + ); + } + $this->reConnectTimes = 0; + } catch (\Exception $e) { + if ($this->reConnectTimes < 4 && $this->isBreak($e)) { + --$this->transTimes; + ++$this->reConnectTimes; + $this->close()->startTrans(); + } + throw $e; + } + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit(): void + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->commit(); + } + + --$this->transTimes; + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback(): void + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->rollBack(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepointRollBack('trans' . $this->transTimes) + ); + } + + $this->transTimes = max(0, $this->transTimes - 1); + } + + /** + * 是否支持事务嵌套 + * @return bool + */ + protected function supportSavepoint(): bool + { + return false; + } + + /** + * 生成定义保存点的SQL + * @access protected + * @param string $name 标识 + * @return string + */ + protected function parseSavepoint(string $name): string + { + return 'SAVEPOINT ' . $name; + } + + /** + * 生成回滚到保存点的SQL + * @access protected + * @param string $name 标识 + * @return string + */ + protected function parseSavepointRollBack(string $name): string + { + return 'ROLLBACK TO SAVEPOINT ' . $name; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $sqlArray SQL批处理指令 + * @param array $bind 参数绑定 + * @return bool + */ + public function batchQuery(BaseQuery $query, array $sqlArray = [], array $bind = []): bool + { + // 自动启动事务支持 + $this->startTrans(); + + try { + foreach ($sqlArray as $sql) { + $this->execute($query, $sql, $bind); + } + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } + + return true; + } + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close() + { + $this->linkID = null; + $this->linkWrite = null; + $this->linkRead = null; + $this->links = []; + + $this->free(); + + return $this; + } + + /** + * 是否断线 + * @access protected + * @param \PDOException|\Exception $e 异常对象 + * @return bool + */ + protected function isBreak($e): bool + { + if (!$this->config['break_reconnect']) { + return false; + } + + $error = $e->getMessage(); + + foreach ($this->breakMatchStr as $msg) { + if (false !== stripos($error, $msg)) { + return true; + } + } + + return false; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->getRealSql($this->queryStr, $this->bind); + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @param string $sequence 自增序列名 + * @return mixed + */ + public function getLastInsID(BaseQuery $query, string $sequence = null) + { + try { + $insertId = $this->linkID->lastInsertId($sequence); + } catch (\Exception $e) { + $insertId = ''; + } + + return $this->autoInsIDType($query, $insertId); + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @param string $insertId 自增ID + * @return mixed + */ + protected function autoInsIDType(BaseQuery $query, string $insertId) + { + $pk = $query->getAutoInc(); + + if ($pk) { + $type = $this->getFieldBindType($pk); + + if (PDO::PARAM_INT == $type) { + $insertId = (int) $insertId; + } elseif (self::PARAM_FLOAT == $type) { + $insertId = (float) $insertId; + } + } + + return $insertId; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError(): string + { + if ($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $error = $error[1] . ':' . $error[2]; + } else { + $error = ''; + } + + if ('' != $this->queryStr) { + $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); + } + + return $error; + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect(bool $master = true): void + { + if (!empty($this->config['deploy'])) { + // 采用分布式数据库 + if ($master || $this->transTimes) { + if (!$this->linkWrite) { + $this->linkWrite = $this->multiConnect(true); + } + + $this->linkID = $this->linkWrite; + } else { + if (!$this->linkRead) { + $this->linkRead = $this->multiConnect(false); + } + + $this->linkID = $this->linkRead; + } + } elseif (!$this->linkID) { + // 默认单数据库 + $this->linkID = $this->connect(); + } + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return PDO + */ + protected function multiConnect(bool $master = false): PDO + { + $config = []; + + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name]; + } + + // 主服务器序号 + $m = floor(mt_rand(0, $this->config['master_num'] - 1)); + + if ($this->config['rw_separate']) { + // 主从式采用读写分离 + if ($master) // 主服务器写入 + { + $r = $m; + } elseif (is_numeric($this->config['slave_no'])) { + // 指定服务器读 + $r = $this->config['slave_no']; + } else { + // 读操作连接从服务器 每次随机连接的数据库 + $r = floor(mt_rand($this->config['master_num'], count($config['hostname']) - 1)); + } + } else { + // 读写操作不区分服务器 每次随机连接的数据库 + $r = floor(mt_rand(0, count($config['hostname']) - 1)); + } + $dbMaster = false; + + if ($m != $r) { + $dbMaster = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbMaster[$name] = $config[$name][$m] ?? $config[$name][0]; + } + } + + $dbConfig = []; + + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0]; + } + + return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa(string $xid) + {} + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa(string $xid) + {} + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa(string $xid) + {} + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa(string $xid) + {} +} diff --git a/vendor/topthink/think-orm/src/db/Query.php b/vendor/topthink/think-orm/src/db/Query.php new file mode 100644 index 0000000..f039384 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Query.php @@ -0,0 +1,493 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use PDOStatement; +use think\helper\Str; + +/** + * PDO数据查询类 + */ +class Query extends BaseQuery +{ + use concern\JoinAndViewQuery; + use concern\ParamsBind; + use concern\TableFieldInfo; + + /** + * 表达式方式指定Field排序 + * @access public + * @param string $field 排序字段 + * @param array $bind 参数绑定 + * @return $this + */ + public function orderRaw(string $field, array $bind = []) + { + if (!empty($bind)) { + $this->bindParams($field, $bind); + } + + $this->options['order'][] = new Raw($field); + + return $this; + } + + /** + * 表达式方式指定查询字段 + * @access public + * @param string $field 字段名 + * @return $this + */ + public function fieldRaw(string $field) + { + $this->options['field'][] = new Raw($field); + + return $this; + } + + /** + * 指定Field排序 orderField('id',[1,2,3],'desc') + * @access public + * @param string $field 排序字段 + * @param array $values 排序值 + * @param string $order 排序 desc/asc + * @return $this + */ + public function orderField(string $field, array $values, string $order = '') + { + if (!empty($values)) { + $values['sort'] = $order; + + $this->options['order'][$field] = $values; + } + + return $this; + } + + /** + * 随机排序 + * @access public + * @return $this + */ + public function orderRand() + { + $this->options['order'][] = '[rand]'; + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $field 字段名 + * @param string $value 字段值 + * @return $this + */ + public function exp(string $field, string $value) + { + $this->options['data'][$field] = new Raw($value); + return $this; + } + + /** + * 表达式方式指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function tableRaw(string $table) + { + $this->options['table'] = new Raw($table); + + return $this; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return array + * @throws BindParamException + * @throws PDOException + */ + public function query(string $sql, array $bind = []): array + { + return $this->connection->query($this, $sql, $bind); + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return int + * @throws BindParamException + * @throws PDOException + */ + public function execute(string $sql, array $bind = []): int + { + return $this->connection->execute($this, $sql, $bind, true); + } + + /** + * 获取执行的SQL语句而不进行实际的查询 + * @access public + * @param bool $fetch 是否返回sql + * @return $this|Fetch + */ + public function fetchSql(bool $fetch = true) + { + $this->options['fetch_sql'] = $fetch; + + if ($fetch) { + return new Fetch($this); + } + + return $this; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sql SQL批处理指令 + * @return bool + */ + public function batchQuery(array $sql = []): bool + { + return $this->connection->batchQuery($this, $sql); + } + + /** + * USING支持 用于多表删除 + * @access public + * @param mixed $using USING + * @return $this + */ + public function using($using) + { + $this->options['using'] = $using; + return $this; + } + + /** + * 存储过程调用 + * @access public + * @param bool $procedure 是否为存储过程查询 + * @return $this + */ + public function procedure(bool $procedure = true) + { + $this->options['procedure'] = $procedure; + return $this; + } + + /** + * 指定group查询 + * @access public + * @param string|array $group GROUP + * @return $this + */ + public function group($group) + { + $this->options['group'] = $group; + return $this; + } + + /** + * 指定having查询 + * @access public + * @param string $having having + * @return $this + */ + public function having(string $having) + { + $this->options['having'] = $having; + return $this; + } + + /** + * 指定distinct查询 + * @access public + * @param bool $distinct 是否唯一 + * @return $this + */ + public function distinct(bool $distinct = true) + { + $this->options['distinct'] = $distinct; + return $this; + } + + /** + * 设置自增序列名 + * @access public + * @param string $sequence 自增序列名 + * @return $this + */ + public function sequence(string $sequence = null) + { + $this->options['sequence'] = $sequence; + return $this; + } + + /** + * 指定强制索引 + * @access public + * @param string $force 索引名称 + * @return $this + */ + public function force(string $force) + { + $this->options['force'] = $force; + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return $this + */ + public function comment(string $comment) + { + $this->options['comment'] = $comment; + return $this; + } + + /** + * 设置是否REPLACE + * @access public + * @param bool $replace 是否使用REPLACE写入数据 + * @return $this + */ + public function replace(bool $replace = true) + { + $this->options['replace'] = $replace; + return $this; + } + + /** + * 设置当前查询所在的分区 + * @access public + * @param string|array $partition 分区名称 + * @return $this + */ + public function partition($partition) + { + $this->options['partition'] = $partition; + return $this; + } + + /** + * 设置DUPLICATE + * @access public + * @param array|string|Raw $duplicate DUPLICATE信息 + * @return $this + */ + public function duplicate($duplicate) + { + $this->options['duplicate'] = $duplicate; + return $this; + } + + /** + * 设置查询的额外参数 + * @access public + * @param string $extra 额外信息 + * @return $this + */ + public function extra(string $extra) + { + $this->options['extra'] = $extra; + return $this; + } + + /** + * 创建子查询SQL + * @access public + * @param bool $sub 是否添加括号 + * @return string + * @throws Exception + */ + public function buildSql(bool $sub = true): string + { + return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select(); + } + + /** + * 获取当前数据表的主键 + * @access public + * @return string|array + */ + public function getPk() + { + if (empty($this->pk)) { + $this->pk = $this->connection->getPk($this->getTable()); + } + + return $this->pk; + } + + /** + * 指定数据表自增主键 + * @access public + * @param string $autoinc 自增键 + * @return $this + */ + public function autoinc(string $autoinc) + { + $this->autoinc = $autoinc; + return $this; + } + + /** + * 获取当前数据表的自增主键 + * @access public + * @return string + */ + public function getAutoInc() + { + if (empty($this->autoinc)) { + $this->autoinc = $this->connection->getAutoInc($this->getTable()); + } + + return $this->autoinc; + } + + /** + * 字段值增长 + * @access public + * @param string $field 字段名 + * @param float $step 增长值 + * @return $this + */ + public function inc(string $field, float $step = 1) + { + $this->options['data'][$field] = ['INC', $step]; + + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string $field 字段名 + * @param float $step 增长值 + * @return $this + */ + public function dec(string $field, float $step = 1) + { + $this->options['data'][$field] = ['DEC', $step]; + return $this; + } + + /** + * 获取当前的查询标识 + * @access public + * @param mixed $data 要序列化的数据 + * @return string + */ + public function getQueryGuid($data = null): string + { + return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)) . serialize($this->getBind(false))); + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return PDOStatement + */ + public function getPdo(): PDOStatement + { + return $this->connection->pdo($this); + } + + /** + * 使用游标查找记录 + * @access public + * @param mixed $data 数据 + * @return \Generator + */ + public function cursor($data = null) + { + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $connection = clone $this->connection; + + return $connection->cursor($this); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string|array $column 分批处理的字段名 + * @param string $order 字段排序 + * @return bool + * @throws Exception + */ + public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool + { + $options = $this->getOptions(); + $column = $column ?: $this->getPk(); + + if (isset($options['order'])) { + unset($options['order']); + } + + $bind = $this->bind; + + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + $query = $this->options($options)->limit($count); + + if (strpos($column, '.')) { + [$alias, $key] = explode('.', $column); + } else { + $key = $column; + } + } + + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (isset($times)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = $resultSet->pop(); + $lastId = is_array($end) ? $end[$key] : $end->getData($key); + + $query = $this->options($options) + ->limit($count) + ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); + } + + $resultSet = $query->bind($bind)->order($column, $order)->select(); + } + + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/Raw.php b/vendor/topthink/think-orm/src/db/Raw.php new file mode 100644 index 0000000..0091a5d --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Raw.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +/** + * SQL Raw + */ +class Raw +{ + /** + * 查询表达式 + * + * @var string + */ + protected $value; + + /** + * 创建一个查询表达式 + * + * @param string $value + * @return void + */ + public function __construct(string $value) + { + $this->value = $value; + } + + /** + * 获取表达式 + * + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + public function __toString() + { + return (string) $this->value; + } +} diff --git a/vendor/topthink/think-orm/src/db/Where.php b/vendor/topthink/think-orm/src/db/Where.php new file mode 100644 index 0000000..0880460 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Where.php @@ -0,0 +1,182 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use ArrayAccess; + +/** + * 数组查询对象 + */ +class Where implements ArrayAccess +{ + /** + * 查询表达式 + * @var array + */ + protected $where = []; + + /** + * 是否需要把查询条件两边增加括号 + * @var bool + */ + protected $enclose = false; + + /** + * 创建一个查询表达式 + * + * @param array $where 查询条件数组 + * @param bool $enclose 是否增加括号 + */ + public function __construct(array $where = [], bool $enclose = false) + { + $this->where = $where; + $this->enclose = $enclose; + } + + /** + * 设置是否添加括号 + * @access public + * @param bool $enclose + * @return $this + */ + public function enclose(bool $enclose = true) + { + $this->enclose = $enclose; + return $this; + } + + /** + * 解析为Query对象可识别的查询条件数组 + * @access public + * @return array + */ + public function parse(): array + { + $where = []; + + foreach ($this->where as $key => $val) { + if ($val instanceof Raw) { + $where[] = [$key, 'exp', $val]; + } elseif (is_null($val)) { + $where[] = [$key, 'NULL', '']; + } elseif (is_array($val)) { + $where[] = $this->parseItem($key, $val); + } else { + $where[] = [$key, '=', $val]; + } + } + + return $this->enclose ? [$where] : $where; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $field 查询字段 + * @param array $where 查询条件 + * @return array + */ + protected function parseItem(string $field, array $where = []): array + { + $op = $where[0]; + $condition = $where[1] ?? null; + + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($where, $field); + } elseif (is_null($condition)) { + if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif (is_null($op) || '=' == $op) { + $where = [$field, 'NULL', '']; + } elseif ('<>' == $op) { + $where = [$field, 'NOTNULL', '']; + } else { + // 字段相等查询 + $where = [$field, '=', $op]; + } + } else { + $where = [$field, $op, $condition]; + } + + return $where; + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name, $value) + { + $this->where[$name] = $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return $this->where[$name] ?? null; + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return bool + */ + public function __isset($name) + { + return isset($this->where[$name]); + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) + { + unset($this->where[$name]); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->__set($name, $value); + } + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->__get($name); + } + +} diff --git a/vendor/topthink/think-orm/src/db/builder/Mongo.php b/vendor/topthink/think-orm/src/db/builder/Mongo.php new file mode 100644 index 0000000..823156b --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Mongo.php @@ -0,0 +1,675 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\db\builder; + +use MongoDB\BSON\Javascript; +use MongoDB\BSON\ObjectID; +use MongoDB\BSON\Regex; +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\InvalidArgumentException; +use MongoDB\Driver\Query as MongoQuery; +use think\db\connector\Mongo as Connection; +use think\db\exception\DbException as Exception; +use think\db\Mongo as Query; + +class Mongo +{ + // connection对象实例 + protected $connection; + // 最后插入ID + protected $insertId = []; + // 查询表达式 + protected $exp = ['<>' => 'ne', '=' => 'eq', '>' => 'gt', '>=' => 'gte', '<' => 'lt', '<=' => 'lte', 'in' => 'in', 'not in' => 'nin', 'nin' => 'nin', 'mod' => 'mod', 'exists' => 'exists', 'null' => 'null', 'notnull' => 'not null', 'not null' => 'not null', 'regex' => 'regex', 'type' => 'type', 'all' => 'all', '> time' => '> time', '< time' => '< time', 'between' => 'between', 'not between' => 'not between', 'between time' => 'between time', 'not between time' => 'not between time', 'notbetween time' => 'not between time', 'like' => 'like', 'near' => 'near', 'size' => 'size']; + + /** + * 架构函数 + * @access public + * @param Connection $connection 数据库连接对象实例 + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return Connection + */ + public function getConnection(): Connection + { + return $this->connection; + } + + /** + * key分析 + * @access protected + * @param string $key + * @return string + */ + protected function parseKey(Query $query, string $key): string + { + if (0 === strpos($key, '__TABLE__.')) { + [$collection, $key] = explode('.', $key, 2); + } + + if ('id' == $key && $this->connection->getConfig('pk_convert_id')) { + $key = '_id'; + } + + return trim($key); + } + + /** + * value分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseValue(Query $query, $value, $field = '') + { + if ('_id' == $field && 'ObjectID' == $this->connection->getConfig('pk_type') && is_string($value)) { + try { + return new ObjectID($value); + } catch (InvalidArgumentException $e) { + return new ObjectID(); + } + } + + return $value; + } + + /** + * insert数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @return array + */ + protected function parseData(Query $query, array $data): array + { + if (empty($data)) { + return []; + } + + $result = []; + + foreach ($data as $key => $val) { + $item = $this->parseKey($query, $key); + + if (is_object($val)) { + $result[$item] = $val; + } elseif (isset($val[0]) && 'exp' == $val[0]) { + $result[$item] = $val[1]; + } elseif (is_null($val)) { + $result[$item] = 'NULL'; + } else { + $result[$item] = $this->parseValue($query, $val, $key); + } + } + + return $result; + } + + /** + * Set数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @return array + */ + protected function parseSet(Query $query, array $data): array + { + if (empty($data)) { + return []; + } + + $result = []; + + foreach ($data as $key => $val) { + $item = $this->parseKey($query, $key); + + if (is_array($val) && isset($val[0]) && is_string($val[0]) && 0 === strpos($val[0], '$')) { + $result[$val[0]][$item] = $this->parseValue($query, $val[1], $key); + } else { + $result['$set'][$item] = $this->parseValue($query, $val, $key); + } + } + + return $result; + } + + /** + * 生成查询过滤条件 + * @access public + * @param Query $query 查询对象 + * @param mixed $where + * @return array + */ + public function parseWhere(Query $query, array $where): array + { + if (empty($where)) { + $where = []; + } + + $filter = []; + foreach ($where as $logic => $val) { + $logic = '$' . strtolower($logic); + foreach ($val as $field => $value) { + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); + } + $field = array_shift($value); + } elseif (!($value instanceof \Closure)) { + throw new Exception('where express error:' . var_export($value, true)); + } + + if ($value instanceof \Closure) { + // 使用闭包查询 + $query = new Query($this->connection); + call_user_func_array($value, [ & $query]); + $filter[$logic][] = $this->parseWhere($query, $query->getOptions('where')); + } else { + if (strpos($field, '|')) { + // 不同字段使用相同查询条件(OR) + $array = explode('|', $field); + foreach ($array as $k) { + $filter['$or'][] = $this->parseWhereItem($query, $k, $value); + } + } elseif (strpos($field, '&')) { + // 不同字段使用相同查询条件(AND) + $array = explode('&', $field); + foreach ($array as $k) { + $filter['$and'][] = $this->parseWhereItem($query, $k, $value); + } + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $filter[$logic][] = $this->parseWhereItem($query, $field, $value); + } + } + } + } + + $options = $query->getOptions(); + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + [$field, $condition] = $options['soft_delete']; + $filter['$and'][] = $this->parseWhereItem($query, $field, $condition); + } + + return $filter; + } + + // where子单元分析 + protected function parseWhereItem(Query $query, $field, $val): array + { + $key = $field ? $this->parseKey($query, $field) : ''; + // 查询规则和条件 + if (!is_array($val)) { + $val = ['=', $val]; + } + [$exp, $value] = $val; + + // 对一个字段使用多个查询条件 + if (is_array($exp)) { + $data = []; + foreach ($val as $value) { + $exp = $value[0]; + $value = $value[1]; + if (!in_array($exp, $this->exp)) { + $exp = strtolower($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } + } + $k = '$' . $exp; + $data[$k] = $value; + } + $result[$key] = $data; + return $result; + } elseif (!in_array($exp, $this->exp)) { + $exp = strtolower($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } else { + throw new Exception('where express error:' . $exp); + } + } + + $result = []; + if ('=' == $exp) { + // 普通查询 + $result[$key] = $this->parseValue($query, $value, $key); + } elseif (in_array($exp, ['neq', 'ne', 'gt', 'egt', 'gte', 'lt', 'lte', 'elt', 'mod'])) { + // 比较运算 + $k = '$' . $exp; + $result[$key] = [$k => $this->parseValue($query, $value, $key)]; + } elseif ('null' == $exp) { + // NULL 查询 + $result[$key] = null; + } elseif ('not null' == $exp) { + $result[$key] = ['$ne' => null]; + } elseif ('all' == $exp) { + // 满足所有指定条件 + $result[$key] = ['$all', $this->parseValue($query, $value, $key)]; + } elseif ('between' == $exp) { + // 区间查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$gte' => $this->parseValue($query, $value[0], $key), '$lte' => $this->parseValue($query, $value[1], $key)]; + } elseif ('not between' == $exp) { + // 范围查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$lt' => $this->parseValue($query, $value[0], $key), '$gt' => $this->parseValue($query, $value[1], $key)]; + } elseif ('exists' == $exp) { + // 字段是否存在 + $result[$key] = ['$exists' => (bool) $value]; + } elseif ('type' == $exp) { + // 类型查询 + $result[$key] = ['$type' => intval($value)]; + } elseif ('exp' == $exp) { + // 表达式查询 + $result['$where'] = $value instanceof Javascript ? $value : new Javascript($value); + } elseif ('like' == $exp) { + // 模糊查询 采用正则方式 + $result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i'); + } elseif (in_array($exp, ['nin', 'in'])) { + // IN 查询 + $value = is_array($value) ? $value : explode(',', $value); + foreach ($value as $k => $val) { + $value[$k] = $this->parseValue($query, $val, $key); + } + $result[$key] = ['$' . $exp => $value]; + } elseif ('regex' == $exp) { + $result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i'); + } elseif ('< time' == $exp) { + $result[$key] = ['$lt' => $this->parseDateTime($query, $value, $field)]; + } elseif ('> time' == $exp) { + $result[$key] = ['$gt' => $this->parseDateTime($query, $value, $field)]; + } elseif ('between time' == $exp) { + // 区间查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$gte' => $this->parseDateTime($query, $value[0], $field), '$lte' => $this->parseDateTime($query, $value[1], $field)]; + } elseif ('not between time' == $exp) { + // 范围查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$lt' => $this->parseDateTime($query, $value[0], $field), '$gt' => $this->parseDateTime($query, $value[1], $field)]; + } elseif ('near' == $exp) { + // 经纬度查询 + $result[$key] = ['$near' => $this->parseValue($query, $value, $key)]; + } elseif ('size' == $exp) { + // 元素长度查询 + $result[$key] = ['$size' => intval($value)]; + } else { + // 普通查询 + $result[$key] = $this->parseValue($query, $value, $key); + } + + return $result; + } + + /** + * 日期时间条件解析 + * @access protected + * @param Query $query 查询对象 + * @param string $value + * @param string $key + * @return string + */ + protected function parseDateTime(Query $query, $value, $key) + { + // 获取时间字段类型 + $type = $query->getFieldType($key); + + if ($type) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (is_int($value)) { + if (preg_match('/(datetime|timestamp)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + } + + return $value; + } + + /** + * 获取最后写入的ID 如果是insertAll方法的话 返回所有写入的ID + * @access public + * @return mixed + */ + public function getLastInsID() + { + return $this->insertId; + } + + /** + * 生成insert BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @return BulkWrite + */ + public function insert(Query $query): BulkWrite + { + // 分析并处理数据 + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + $bulk = new BulkWrite; + + if ($insertId = $bulk->insert($data)) { + $this->insertId = $insertId; + } + + $this->log('insert', $data, $options); + + return $bulk; + } + + /** + * 生成insertall BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @return BulkWrite + */ + public function insertAll(Query $query, array $dataSet): BulkWrite + { + $bulk = new BulkWrite; + $options = $query->getOptions(); + + $this->insertId = []; + foreach ($dataSet as $data) { + // 分析并处理数据 + $data = $this->parseData($query, $data); + if ($insertId = $bulk->insert($data)) { + $this->insertId[] = $insertId; + } + } + + $this->log('insert', $dataSet, $options); + + return $bulk; + } + + /** + * 生成update BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @return BulkWrite + */ + public function update(Query $query): BulkWrite + { + $options = $query->getOptions(); + + $data = $this->parseSet($query, $options['data']); + $where = $this->parseWhere($query, $options['where']); + + if (1 == $options['limit']) { + $updateOptions = ['multi' => false]; + } else { + $updateOptions = ['multi' => true]; + } + + $bulk = new BulkWrite; + + $bulk->update($where, $data, $updateOptions); + + $this->log('update', $data, $where); + + return $bulk; + } + + /** + * 生成delete BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @return BulkWrite + */ + public function delete(Query $query): BulkWrite + { + $options = $query->getOptions(); + $where = $this->parseWhere($query, $options['where']); + + $bulk = new BulkWrite; + + if (1 == $options['limit']) { + $deleteOptions = ['limit' => 1]; + } else { + $deleteOptions = ['limit' => 0]; + } + + $bulk->delete($where, $deleteOptions); + + $this->log('remove', $where, $deleteOptions); + + return $bulk; + } + + /** + * 生成Mongo查询对象 + * @access public + * @param Query $query 查询对象 + * @param bool $one 是否仅获取一个记录 + * @return MongoQuery + */ + public function select(Query $query, bool $one = false): MongoQuery + { + $options = $query->getOptions(); + + $where = $this->parseWhere($query, $options['where']); + + if ($one) { + $options['limit'] = 1; + } + + $query = new MongoQuery($where, $options); + + $this->log('find', $where, $options); + + return $query; + } + + /** + * 生成Count命令 + * @access public + * @param Query $query 查询对象 + * @return Command + */ + public function count(Query $query): Command + { + $options = $query->getOptions(); + + $cmd['count'] = $options['table']; + $cmd['query'] = (object) $this->parseWhere($query, $options['where']); + + foreach (['hint', 'limit', 'maxTimeMS', 'skip'] as $option) { + if (isset($options[$option])) { + $cmd[$option] = $options[$option]; + } + } + + $command = new Command($cmd); + $this->log('cmd', 'count', $cmd); + + return $command; + } + + /** + * 聚合查询命令 + * @access public + * @param Query $query 查询对象 + * @param array $extra 指令和字段 + * @return Command + */ + public function aggregate(Query $query, array $extra): Command + { + $options = $query->getOptions(); + [$fun, $field] = $extra; + + if ('id' == $field && $this->connection->getConfig('pk_convert_id')) { + $field = '_id'; + } + + $group = isset($options['group']) ? '$' . $options['group'] : null; + + $pipeline = [ + ['$match' => (object) $this->parseWhere($query, $options['where'])], + ['$group' => ['_id' => $group, 'aggregate' => ['$' . $fun => '$' . $field]]], + ]; + + $cmd = [ + 'aggregate' => $options['table'], + 'allowDiskUse' => true, + 'pipeline' => $pipeline, + 'cursor' => new \stdClass, + ]; + + foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) { + if (isset($options[$option])) { + $cmd[$option] = $options[$option]; + } + } + + $command = new Command($cmd); + + $this->log('aggregate', $cmd); + + return $command; + } + + /** + * 多聚合查询命令, 可以对多个字段进行 group by 操作 + * + * @param Query $query 查询对象 + * @param array $extra 指令和字段 + * @return Command + */ + public function multiAggregate(Query $query, $extra): Command + { + $options = $query->getOptions(); + + [$aggregate, $groupBy] = $extra; + + $groups = ['_id' => []]; + + foreach ($groupBy as $field) { + $groups['_id'][$field] = '$' . $field; + } + + foreach ($aggregate as $fun => $field) { + $groups[$field . '_' . $fun] = ['$' . $fun => '$' . $field]; + } + + $pipeline = [ + ['$match' => (object) $this->parseWhere($query, $options['where'])], + ['$group' => $groups], + ]; + + $cmd = [ + 'aggregate' => $options['table'], + 'allowDiskUse' => true, + 'pipeline' => $pipeline, + 'cursor' => new \stdClass, + ]; + + foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) { + if (isset($options[$option])) { + $cmd[$option] = $options[$option]; + } + } + + $command = new Command($cmd); + $this->log('group', $cmd); + + return $command; + } + + /** + * 生成distinct命令 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 + * @return Command + */ + public function distinct(Query $query, $field): Command + { + $options = $query->getOptions(); + + $cmd = [ + 'distinct' => $options['table'], + 'key' => $field, + ]; + + if (!empty($options['where'])) { + $cmd['query'] = (object) $this->parseWhere($query, $options['where']); + } + + if (isset($options['maxTimeMS'])) { + $cmd['maxTimeMS'] = $options['maxTimeMS']; + } + + $command = new Command($cmd); + + $this->log('cmd', 'distinct', $cmd); + + return $command; + } + + /** + * 查询所有的collection + * @access public + * @return Command + */ + public function listcollections(): Command + { + $cmd = ['listCollections' => 1]; + $command = new Command($cmd); + + $this->log('cmd', 'listCollections', $cmd); + + return $command; + } + + /** + * 查询数据表的状态信息 + * @access public + * @param Query $query 查询对象 + * @return Command + */ + public function collStats(Query $query): Command + { + $options = $query->getOptions(); + + $cmd = ['collStats' => $options['table']]; + $command = new Command($cmd); + + $this->log('cmd', 'collStats', $cmd); + + return $command; + } + + protected function log($type, $data, $options = []) + { + $this->connection->mongoLog($type, $data, $options); + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Mysql.php b/vendor/topthink/think-orm/src/db/builder/Mysql.php new file mode 100644 index 0000000..977ff9b --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Mysql.php @@ -0,0 +1,421 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\exception\DbException as Exception; +use think\db\Query; +use think\db\Raw; + +/** + * mysql数据库驱动 + */ +class Mysql extends Builder +{ + /** + * 查询表达式解析 + * @var array + */ + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseRegexp' => ['REGEXP', 'NOT REGEXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + 'parseFindInSet' => ['FIND IN SET'], + ]; + + /** + * SELECT SQL表达式 + * @var string + */ + protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%PARTITION%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% SET %SET% %DUPLICATE%%COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% (%FIELD%) VALUES %DATA% %DUPLICATE%%COMMENT%'; + + /** + * UPDATE SQL表达式 + * @var string + */ + protected $updateSql = 'UPDATE%EXTRA% %TABLE%%PARTITION% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * DELETE SQL表达式 + * @var string + */ + protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%PARTITION%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 生成查询SQL + * @access public + * @param Query $query 查询对象 + * @param bool $one 是否仅获取一个记录 + * @return string + */ + public function select(Query $query, bool $one = false): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%PARTITION%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + $this->parseDistinct($query, $options['distinct']), + $this->parseExtra($query, $options['extra']), + $this->parseField($query, $options['field']), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseGroup($query, $options['group']), + $this->parseHaving($query, $options['having']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $one ? '1' : $options['limit']), + $this->parseUnion($query, $options['union']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + $this->parseForce($query, $options['force']), + ], + $this->selectSql); + } + + /** + * 生成Insert SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function insert(Query $query): string + { + $options = $query->getOptions(); + + // 分析并处理数据 + $data = $this->parseData($query, $options['data']); + if (empty($data)) { + return ''; + } + + $set = []; + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%SET%', '%DUPLICATE%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseExtra($query, $options['extra']), + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + implode(' , ', $set), + $this->parseDuplicate($query, $options['duplicate']), + $this->parseComment($query, $options['comment']), + ], + $this->insertSql); + } + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @param bool $replace 是否replace + * @return string + */ + public function insertAll(Query $query, array $dataSet, bool $replace = false): string + { + $options = $query->getOptions(); + + // 获取绑定信息 + $bind = $query->getFieldsBindType(); + + // 获取合法的字段 + if ('*' == $options['field']) { + $allowFields = array_keys($bind); + } else { + $allowFields = $options['field']; + } + + $fields = []; + $values = []; + + foreach ($dataSet as $data) { + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = '( ' . implode(',', array_values($data)) . ' )'; + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + + return str_replace( + ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseExtra($query, $options['extra']), + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseDuplicate($query, $options['duplicate']), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); + } + + /** + * 生成update SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function update(Query $query): string + { + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + if (empty($data)) { + return ''; + } + $set = []; + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%TABLE%', '%PARTITION%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $set), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->updateSql); + } + + /** + * 生成delete SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function delete(Query $query): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%PARTITION%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + $this->parseExtra($query, $options['extra']), + !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->deleteSql); + } + + /** + * 正则查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseRegexp(Query $query, string $key, string $exp, $value, string $field): string + { + if ($value instanceof Raw) { + $value = $value->getValue(); + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * FIND_IN_SET 查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseFindInSet(Query $query, string $key, string $exp, $value, string $field): string + { + if ($value instanceof Raw) { + $value = $value->getValue(); + } + + return 'FIND_IN_SET(' . $value . ', ' . $key . ')'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $key->getValue(); + } + + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode('->', $key, 2); + return 'json_extract(' . $this->parseKey($query, $field) . ', \'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->', '.', $name) . '\')'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { + $key = '`' . $key . '`'; + } + + if (isset($table)) { + if (strpos($table, '.')) { + $table = str_replace('.', '`.`', $table); + } + + $key = '`' . $table . '`.' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'rand()'; + } + + /** + * Partition 分析 + * @access protected + * @param Query $query 查询对象 + * @param string|array $partition 分区 + * @return string + */ + protected function parsePartition(Query $query, $partition): string + { + if ('' == $partition) { + return ''; + } + + if (is_string($partition)) { + $partition = explode(',', $partition); + } + + return ' PARTITION (' . implode(' , ', $partition) . ') '; + } + + /** + * ON DUPLICATE KEY UPDATE 分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $duplicate + * @return string + */ + protected function parseDuplicate(Query $query, $duplicate): string + { + if ('' == $duplicate) { + return ''; + } + + if ($duplicate instanceof Raw) { + return ' ON DUPLICATE KEY UPDATE ' . $duplicate->getValue() . ' '; + } + + if (is_string($duplicate)) { + $duplicate = explode(',', $duplicate); + } + + $updates = []; + foreach ($duplicate as $key => $val) { + if (is_numeric($key)) { + $val = $this->parseKey($query, $val); + $updates[] = $val . ' = VALUES(' . $val . ')'; + } elseif ($val instanceof Raw) { + $updates[] = $this->parseKey($query, $key) . " = " . $val->getValue(); + } else { + $name = $query->bindValue($val, $query->getConnection()->getFieldBindType($key)); + $updates[] = $this->parseKey($query, $key) . " = :" . $name; + } + } + + return ' ON DUPLICATE KEY UPDATE ' . implode(' , ', $updates) . ' '; + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Oracle.php b/vendor/topthink/think-orm/src/db/builder/Oracle.php new file mode 100644 index 0000000..77ad3c8 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Oracle.php @@ -0,0 +1,95 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; + +/** + * Oracle数据库驱动 + */ +class Oracle extends Builder +{ + protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%'; + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, string $limit): string + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + + if (count($limit) > 1) { + $limitStr = "(numrow>" . $limit[0] . ") AND (numrow<=" . ($limit[0] + $limit[1]) . ")"; + } else { + $limitStr = "(numrow>0 AND numrow<=" . $limit[0] . ")"; + } + + } + + return $limitStr ? ' WHERE ' . $limitStr : ''; + } + + /** + * 设置锁机制 + * @access protected + * @param Query $query 查询对象 + * @param bool|false $lock + * @return string + */ + protected function parseLock(Query $query, $lock = false): string + { + if (!$lock) { + return ''; + } + + return ' FOR UPDATE NOWAIT '; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param string $key + * @param string $strict + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode($key, '->'); + $key = $field . '."' . $name . '"'; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'DBMS_RANDOM.value'; + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Pgsql.php b/vendor/topthink/think-orm/src/db/builder/Pgsql.php new file mode 100644 index 0000000..4b581df --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Pgsql.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; +use think\db\Raw; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Builder +{ + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + public function parseLimit(Query $query, string $limit): string + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + + return $limitStr; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $key->getValue(); + } + + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode('->', $key); + $key = '"' . $field . '"' . '->>\'' . $name . '\''; + } elseif (strpos($key, '.')) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + + if ('*' != $key && !preg_match('/[,\"\*\(\).\s]/', $key)) { + $key = '"' . $key . '"'; + } + } + + if (isset($table)) { + $key = $table . '.' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'RANDOM()'; + } + +} diff --git a/vendor/topthink/think-orm/src/db/builder/Sqlite.php b/vendor/topthink/think-orm/src/db/builder/Sqlite.php new file mode 100644 index 0000000..87a84eb --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Sqlite.php @@ -0,0 +1,97 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; +use think\db\Raw; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Builder +{ + /** + * limit + * @access public + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + public function parseLimit(Query $query, string $limit): string + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + + return $limitStr; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'RANDOM()'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $key->getValue(); + } + + $key = trim($key); + + if (strpos($key, '.')) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if (isset($table)) { + $key = $table . '.' . $key; + } + + return $key; + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php b/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php new file mode 100644 index 0000000..80cb166 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php @@ -0,0 +1,184 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\exception\DbException as Exception; +use think\db\Query; +use think\db\Raw; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Builder +{ + /** + * SELECT SQL表达式 + * @var string + */ + protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; + /** + * SELECT INSERT SQL表达式 + * @var string + */ + protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; + + /** + * UPDATE SQL表达式 + * @var string + */ + protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + + /** + * DELETE SQL表达式 + * @var string + */ + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * order分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $order + * @return string + */ + protected function parseOrder(Query $query, array $order): string + { + if (empty($order)) { + return ' ORDER BY rand()'; + } + + $array = []; + + foreach ($order as $key => $val) { + if ($val instanceof Raw) { + $array[] = $val->getValue(); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } else { + if (is_numeric($key)) { + [$key, $sort] = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + $sort = in_array(strtolower($sort), ['asc', 'desc'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } + } + + return ' ORDER BY ' . implode(',', $array); + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'rand()'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $key->getValue(); + } + + $key = trim($key); + + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { + $key = '[' . $key . ']'; + } + + if (isset($table)) { + $key = '[' . $table . '].' . $key; + } + + return $key; + } + + /** + * limit + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, string $limit): string + { + if (empty($limit)) { + return ''; + } + + $limit = explode(',', $limit); + + if (count($limit) > 1) { + $limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')'; + } else { + $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")"; + } + + return 'WHERE ' . $limitStr; + } + + public function selectInsert(Query $query, array $fields, string $table): string + { + $this->selectSql = $this->selectInsertSql; + + return parent::selectInsert($query, $fields, $table); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php b/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php new file mode 100644 index 0000000..dabfb92 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php @@ -0,0 +1,107 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use think\db\Raw; + +/** + * 聚合查询 + */ +trait AggregateQuery +{ + /** + * 聚合查询 + * @access protected + * @param string $aggregate 聚合方法 + * @param string|Raw $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + protected function aggregate(string $aggregate, $field, bool $force = false) + { + return $this->connection->aggregate($this, $aggregate, $field, $force); + } + + /** + * COUNT查询 + * @access public + * @param string|Raw $field 字段名 + * @return int + */ + public function count(string $field = '*'): int + { + if (!empty($this->options['group'])) { + // 支持GROUP + $options = $this->getOptions(); + $subSql = $this->options($options) + ->field('count(' . $field . ') AS think_count') + ->bind($this->bind) + ->buildSql(); + + $query = $this->newQuery()->table([$subSql => '_group_count_']); + + $count = $query->aggregate('COUNT', '*'); + } else { + $count = $this->aggregate('COUNT', $field); + } + + return (int) $count; + } + + /** + * SUM查询 + * @access public + * @param string|Raw $field 字段名 + * @return float + */ + public function sum($field): float + { + return $this->aggregate('SUM', $field, true); + } + + /** + * MIN查询 + * @access public + * @param string|Raw $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function min($field, bool $force = true) + { + return $this->aggregate('MIN', $field, $force); + } + + /** + * MAX查询 + * @access public + * @param string|Raw $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function max($field, bool $force = true) + { + return $this->aggregate('MAX', $field, $force); + } + + /** + * AVG查询 + * @access public + * @param string|Raw $field 字段名 + * @return float + */ + public function avg($field): float + { + return $this->aggregate('AVG', $field, true); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php b/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php new file mode 100644 index 0000000..c33d1ed --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php @@ -0,0 +1,229 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use think\db\Raw; +use think\helper\Str; + +/** + * JOIN和VIEW查询 + */ +trait JoinAndViewQuery +{ + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param string $type JOIN类型 + * @param array $bind 参数绑定 + * @return $this + */ + public function join($join, string $condition = null, string $type = 'INNER', array $bind = []) + { + $table = $this->getJoinTable($join); + + if (!empty($bind) && $condition) { + $this->bindParams($condition, $bind); + } + + $this->options['join'][] = [$table, strtoupper($type), $condition]; + + return $this; + } + + /** + * LEFT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function leftJoin($join, string $condition = null, array $bind = []) + { + return $this->join($join, $condition, 'LEFT', $bind); + } + + /** + * RIGHT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function rightJoin($join, string $condition = null, array $bind = []) + { + return $this->join($join, $condition, 'RIGHT', $bind); + } + + /** + * FULL JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function fullJoin($join, string $condition = null, array $bind = []) + { + return $this->join($join, $condition, 'FULL'); + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'table alias' + * @access protected + * @param array|string|Raw $join JION表名 + * @param string $alias 别名 + * @return string|array + */ + protected function getJoinTable($join, &$alias = null) + { + if (is_array($join)) { + $table = $join; + $alias = array_shift($join); + return $table; + } elseif ($join instanceof Raw) { + return $join; + } + + $join = trim($join); + + if (false !== strpos($join, '(')) { + // 使用子查询 + $table = $join; + } else { + // 使用别名 + if (strpos($join, ' ')) { + // 使用别名 + [$table, $alias] = explode(' ', $join); + } else { + $table = $join; + if (false === strpos($join, '.')) { + $alias = $join; + } + } + + if ($this->prefix && false === strpos($table, '.') && 0 !== strpos($table, $this->prefix)) { + $table = $this->getTable($table); + } + } + + if (!empty($alias) && $table != $alias) { + $table = [$table => $alias]; + } + + return $table; + } + + /** + * 指定JOIN查询字段 + * @access public + * @param string|array $join 数据表 + * @param string|array $field 查询字段 + * @param string $on JOIN条件 + * @param string $type JOIN类型 + * @param array $bind 参数绑定 + * @return $this + */ + public function view($join, $field = true, $on = null, string $type = 'INNER', array $bind = []) + { + $this->options['view'] = true; + + $fields = []; + $table = $this->getJoinTable($join, $alias); + + if (true === $field) { + $fields = $alias . '.*'; + } else { + if (is_string($field)) { + $field = explode(',', $field); + } + + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $fields[] = $alias . '.' . $val; + + $this->options['map'][$val] = $alias . '.' . $val; + } else { + if (preg_match('/[,=\.\'\"\(\s]/', $key)) { + $name = $key; + } else { + $name = $alias . '.' . $key; + } + + $fields[] = $name . ' AS ' . $val; + + $this->options['map'][$val] = $name; + } + } + } + + $this->field($fields); + + if ($on) { + $this->join($table, $on, $type, $bind); + } else { + $this->table($table); + } + + return $this; + } + + /** + * 视图查询处理 + * @access protected + * @param array $options 查询参数 + * @return void + */ + protected function parseView(array &$options): void + { + foreach (['AND', 'OR'] as $logic) { + if (isset($options['where'][$logic])) { + foreach ($options['where'][$logic] as $key => $val) { + if (array_key_exists($key, $options['map'])) { + array_shift($val); + array_unshift($val, $options['map'][$key]); + $options['where'][$logic][$options['map'][$key]] = $val; + unset($options['where'][$logic][$key]); + } + } + } + } + + if (isset($options['order'])) { + // 视图查询排序处理 + foreach ($options['order'] as $key => $val) { + if (is_numeric($key) && is_string($val)) { + if (strpos($val, ' ')) { + [$field, $sort] = explode(' ', $val); + if (array_key_exists($field, $options['map'])) { + $options['order'][$options['map'][$field]] = $sort; + unset($options['order'][$key]); + } + } elseif (array_key_exists($val, $options['map'])) { + $options['order'][$options['map'][$val]] = 'asc'; + unset($options['order'][$key]); + } + } elseif (array_key_exists($key, $options['map'])) { + $options['order'][$options['map'][$key]] = $val; + unset($options['order'][$key]); + } + } + } + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php new file mode 100644 index 0000000..92e4417 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php @@ -0,0 +1,516 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use Closure; +use think\helper\Str; +use think\Model; +use think\model\Collection as ModelCollection; + +/** + * 模型及关联查询 + */ +trait ModelRelationQuery +{ + + /** + * 当前模型对象 + * @var Model + */ + protected $model; + + /** + * 指定模型 + * @access public + * @param Model $model 模型对象实例 + * @return $this + */ + public function model(Model $model) + { + $this->model = $model; + return $this; + } + + /** + * 获取当前的模型对象 + * @access public + * @return Model|null + */ + public function getModel() + { + return $this->model; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 需要隐藏的字段名 + * @return $this + */ + public function hidden(array $hidden) + { + $this->options['hidden'] = $hidden; + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible 需要输出的属性 + * @return $this + */ + public function visible(array $visible) + { + $this->options['visible'] = $visible; + return $this; + } + + /** + * 设置需要追加输出的属性 + * @access public + * @param array $append 需要追加的属性 + * @return $this + */ + public function append(array $append) + { + $this->options['append'] = $append; + return $this; + } + + /** + * 添加查询范围 + * @access public + * @param array|string|Closure $scope 查询范围定义 + * @param array $args 参数 + * @return $this + */ + public function scope($scope, ...$args) + { + // 查询范围的第一个参数始终是当前查询对象 + array_unshift($args, $this); + + if ($scope instanceof Closure) { + call_user_func_array($scope, $args); + return $this; + } + + if (is_string($scope)) { + $scope = explode(',', $scope); + } + + if ($this->model) { + // 检查模型类的查询范围方法 + foreach ($scope as $name) { + $method = 'scope' . trim($name); + + if (method_exists($this->model, $method)) { + call_user_func_array([$this->model, $method], $args); + } + } + } + + return $this; + } + + /** + * 设置关联查询 + * @access public + * @param array $relation 关联名称 + * @return $this + */ + public function relation(array $relation) + { + if (!empty($relation)) { + $this->options['relation'] = $relation; + } + + return $this; + } + + /** + * 使用搜索器条件搜索字段 + * @access public + * @param array $fields 搜索字段 + * @param array $data 搜索数据 + * @param string $prefix 字段前缀标识 + * @return $this + */ + public function withSearch(array $fields, array $data = [], string $prefix = '') + { + foreach ($fields as $key => $field) { + if ($field instanceof Closure) { + $field($this, $data[$key] ?? null, $data, $prefix); + } elseif ($this->model) { + // 检测搜索器 + $fieldName = is_numeric($key) ? $field : $key; + $method = 'search' . Str::studly($fieldName) . 'Attr'; + + if (method_exists($this->model, $method)) { + $this->model->$method($this, $data[$field] ?? null, $data, $prefix); + } + } + } + + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, callable $callback = null) + { + if (is_array($name)) { + $this->options['with_attr'] = $name; + } else { + $this->options['with_attr'][$name] = $callback; + } + + return $this; + } + + /** + * 关联预载入 In方式 + * @access public + * @param array|string $with 关联方法名称 + * @return $this + */ + public function with($with) + { + if (!empty($with)) { + $this->options['with'] = (array) $with; + } + + return $this; + } + + /** + * 关联预载入 JOIN方式 + * @access protected + * @param array|string $with 关联方法名 + * @param string $joinType JOIN方式 + * @return $this + */ + public function withJoin($with, string $joinType = '') + { + if (empty($with)) { + return $this; + } + + $with = (array) $with; + $first = true; + + foreach ($with as $key => $relation) { + $closure = null; + $field = true; + + if ($relation instanceof Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } elseif (is_array($relation)) { + $field = $relation; + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + $relation = strstr($relation, '.', true); + } + + $result = $this->model->eagerly($this, $relation, $field, $joinType, $closure, $first); + + if (!$result) { + unset($with[$key]); + } else { + $first = false; + } + } + + $this->via(); + + $this->options['with_join'] = $with; + + return $this; + } + + /** + * 关联统计 + * @access protected + * @param array|string $relations 关联方法名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true) + { + if (!$subQuery) { + $this->options['with_count'][] = [$relations, $aggregate, $field]; + } else { + if (!isset($this->options['field'])) { + $this->field('*'); + } + + $this->model->relationCount($this, (array) $relations, $aggregate, $field, true); + } + + return $this; + } + + /** + * 关联缓存 + * @access public + * @param string|array|bool $relation 关联方法名 + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 + * @return $this + */ + public function withCache($relation = true, $key = true, $expire = null, string $tag = null) + { + if (false === $relation || false === $key || !$this->getConnection()->getCache()) { + return $this; + } + + if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + if (true === $relation || is_numeric($relation)) { + $this->options['with_cache'] = $relation; + return $this; + } + + $relations = (array) $relation; + foreach ($relations as $name => $relation) { + if (!is_numeric($name)) { + $this->options['with_cache'][$name] = is_array($relation) ? $relation : [$key, $relation, $tag]; + } else { + $this->options['with_cache'][$relation] = [$key, $expire, $tag]; + } + } + + return $this; + } + + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, bool $subQuery = true) + { + return $this->withAggregate($relation, 'count', '*', $subQuery); + } + + /** + * 关联统计Sum + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withSum($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'sum', $field, $subQuery); + } + + /** + * 关联统计Max + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMax($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'max', $field, $subQuery); + } + + /** + * 关联统计Min + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMin($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'min', $field, $subQuery); + } + + /** + * 关联统计Avg + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withAvg($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'avg', $field, $subQuery); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return $this + */ + public function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '') + { + return $this->model->has($relation, $operator, $count, $id, $joinType, $this); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @return $this + */ + public function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '') + { + return $this->model->hasWhere($relation, $where, $fields, $joinType, $this); + } + + /** + * 查询数据转换为模型数据集对象 + * @access protected + * @param array $resultSet 数据集 + * @return ModelCollection + */ + protected function resultSetToModelCollection(array $resultSet): ModelCollection + { + if (empty($resultSet)) { + return $this->model->toCollection(); + } + + // 检查动态获取器 + if (!empty($this->options['with_attr'])) { + foreach ($this->options['with_attr'] as $name => $val) { + if (strpos($name, '.')) { + [$relation, $field] = explode('.', $name); + + $withRelationAttr[$relation][$field] = $val; + unset($this->options['with_attr'][$name]); + } + } + } + + $withRelationAttr = $withRelationAttr ?? []; + + foreach ($resultSet as $key => &$result) { + // 数据转换为模型对象 + $this->resultToModel($result, $this->options, true, $withRelationAttr); + } + + if (!empty($this->options['with'])) { + // 预载入 + $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr, false, $this->options['with_cache'] ?? false); + } + + if (!empty($this->options['with_join'])) { + // 预载入 + $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true, $this->options['with_cache'] ?? false); + } + + // 模型数据集转换 + return $this->model->toCollection($resultSet); + } + + /** + * 查询数据转换为模型对象 + * @access protected + * @param array $result 查询数据 + * @param array $options 查询参数 + * @param bool $resultSet 是否为数据集查询 + * @param array $withRelationAttr 关联字段获取器 + * @return void + */ + protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void + { + // 动态获取器 + if (!empty($options['with_attr']) && empty($withRelationAttr)) { + foreach ($options['with_attr'] as $name => $val) { + if (strpos($name, '.')) { + [$relation, $field] = explode('.', $name); + + $withRelationAttr[$relation][$field] = $val; + unset($options['with_attr'][$name]); + } + } + } + + // JSON 数据处理 + if (!empty($options['json'])) { + $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr); + } + + $result = $this->model + ->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options)); + + // 动态获取器 + if (!empty($options['with_attr'])) { + $result->withAttribute($options['with_attr']); + } + + // 输出属性控制 + if (!empty($options['visible'])) { + $result->visible($options['visible']); + } elseif (!empty($options['hidden'])) { + $result->hidden($options['hidden']); + } + + if (!empty($options['append'])) { + $result->append($options['append']); + } + + // 关联查询 + if (!empty($options['relation'])) { + $result->relationQuery($options['relation'], $withRelationAttr); + } + + // 预载入查询 + if (!$resultSet && !empty($options['with'])) { + $result->eagerlyResult($result, $options['with'], $withRelationAttr, false, $options['with_cache'] ?? false); + } + + // JOIN预载入查询 + if (!$resultSet && !empty($options['with_join'])) { + $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true, $options['with_cache'] ?? false); + } + + // 关联统计 + if (!empty($options['with_count'])) { + foreach ($options['with_count'] as $val) { + $result->relationCount($this, (array) $val[0], $val[1], $val[2], false); + } + } + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/ParamsBind.php b/vendor/topthink/think-orm/src/db/concern/ParamsBind.php new file mode 100644 index 0000000..2ae858c --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/ParamsBind.php @@ -0,0 +1,106 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use PDO; + +/** + * 参数绑定支持 + */ +trait ParamsBind +{ + /** + * 当前参数绑定 + * @var array + */ + protected $bind = []; + + /** + * 批量参数绑定 + * @access public + * @param array $value 绑定变量值 + * @return $this + */ + public function bind(array $value) + { + $this->bind = array_merge($this->bind, $value); + return $this; + } + + /** + * 单个参数绑定 + * @access public + * @param mixed $value 绑定变量值 + * @param integer $type 绑定类型 + * @param string $name 绑定标识 + * @return string + */ + public function bindValue($value, int $type = null, string $name = null) + { + $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_' . mt_rand() . '_'; + + $this->bind[$name] = [$value, $type ?: PDO::PARAM_STR]; + return $name; + } + + /** + * 检测参数是否已经绑定 + * @access public + * @param string $key 参数名 + * @return bool + */ + public function isBind($key) + { + return isset($this->bind[$key]); + } + + /** + * 参数绑定 + * @access public + * @param string $sql 绑定的sql表达式 + * @param array $bind 参数绑定 + * @return void + */ + protected function bindParams(string &$sql, array $bind = []): void + { + foreach ($bind as $key => $value) { + if (is_array($value)) { + $name = $this->bindValue($value[0], $value[1], $value[2] ?? null); + } else { + $name = $this->bindValue($value); + } + + if (is_numeric($key)) { + $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1); + } else { + $sql = str_replace(':' . $key, ':' . $name, $sql); + } + } + } + + /** + * 获取绑定的参数 并清空 + * @access public + * @param bool $clear 是否清空绑定数据 + * @return array + */ + public function getBind(bool $clear = true): array + { + $bind = $this->bind; + if ($clear) { + $this->bind = []; + } + + return $bind; + } +} diff --git a/vendor/topthink/think-orm/src/db/concern/ResultOperation.php b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php new file mode 100644 index 0000000..f761d1e --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php @@ -0,0 +1,248 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use think\Collection; +use think\db\exception\DataNotFoundException; +use think\db\exception\ModelNotFoundException; +use think\helper\Str; + +/** + * 查询数据处理 + */ +trait ResultOperation +{ + /** + * 是否允许返回空数据(或空模型) + * @access public + * @param bool $allowEmpty 是否允许为空 + * @return $this + */ + public function allowEmpty(bool $allowEmpty = true) + { + $this->options['allow_empty'] = $allowEmpty; + return $this; + } + + /** + * 设置查询数据不存在是否抛出异常 + * @access public + * @param bool $fail 数据不存在是否抛出异常 + * @return $this + */ + public function failException(bool $fail = true) + { + $this->options['fail'] = $fail; + return $this; + } + + /** + * 处理数据 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function result(array &$result): void + { + if (!empty($this->options['json'])) { + $this->jsonResult($result, $this->options['json'], true); + } + + if (!empty($this->options['with_attr'])) { + $this->getResultAttr($result, $this->options['with_attr']); + } + + $this->filterResult($result); + } + + /** + * 处理数据集 + * @access public + * @param array $resultSet 数据集 + * @return void + */ + protected function resultSet(array &$resultSet): void + { + if (!empty($this->options['json'])) { + foreach ($resultSet as &$result) { + $this->jsonResult($result, $this->options['json'], true); + } + } + + if (!empty($this->options['with_attr'])) { + foreach ($resultSet as &$result) { + $this->getResultAttr($result, $this->options['with_attr']); + } + } + + if (!empty($this->options['visible']) || !empty($this->options['hidden'])) { + foreach ($resultSet as &$result) { + $this->filterResult($result); + } + } + + // 返回Collection对象 + $resultSet = new Collection($resultSet); + } + + /** + * 处理数据的可见和隐藏 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function filterResult(&$result): void + { + if (!empty($this->options['visible'])) { + foreach ($this->options['visible'] as $key) { + $array[] = $key; + } + $result = array_intersect_key($result, array_flip($array)); + } elseif (!empty($this->options['hidden'])) { + foreach ($this->options['hidden'] as $key) { + $array[] = $key; + } + $result = array_diff_key($result, array_flip($array)); + } + } + + /** + * 使用获取器处理数据 + * @access protected + * @param array $result 查询数据 + * @param array $withAttr 字段获取器 + * @return void + */ + protected function getResultAttr(array &$result, array $withAttr = []): void + { + foreach ($withAttr as $name => $closure) { + $name = Str::snake($name); + + if (strpos($name, '.')) { + // 支持JSON字段 获取器定义 + [$key, $field] = explode('.', $name); + + if (isset($result[$key])) { + $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]); + } + } else { + $result[$name] = $closure($result[$name] ?? null, $result); + } + } + } + + /** + * 处理空数据 + * @access protected + * @return array|Model|null + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function resultToEmpty() + { + if (!empty($this->options['fail'])) { + $this->throwNotFound(); + } elseif (!empty($this->options['allow_empty'])) { + return !empty($this->model) ? $this->model->newInstance() : []; + } + } + + /** + * 查找单条记录 不存在返回空数据(或者空模型) + * @access public + * @param mixed $data 数据 + * @return array|Model + */ + public function findOrEmpty($data = null) + { + return $this->allowEmpty(true)->find($data); + } + + /** + * JSON字段数据转换 + * @access protected + * @param array $result 查询数据 + * @param array $json JSON字段 + * @param bool $assoc 是否转换为数组 + * @param array $withRelationAttr 关联获取器 + * @return void + */ + protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void + { + foreach ($json as $name) { + if (!isset($result[$name])) { + continue; + } + + $result[$name] = json_decode($result[$name], true); + + if (isset($withRelationAttr[$name])) { + foreach ($withRelationAttr[$name] as $key => $closure) { + $result[$name][$key] = $closure($result[$name][$key] ?? null, $result[$name]); + } + } + + if (!$assoc) { + $result[$name] = (object) $result[$name]; + } + } + } + + /** + * 查询失败 抛出异常 + * @access protected + * @return void + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function throwNotFound(): void + { + if (!empty($this->model)) { + $class = get_class($this->model); + throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options); + } + + $table = $this->getTable(); + throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options); + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|Closure $data 数据 + * @return array|Model + * @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|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php b/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php new file mode 100644 index 0000000..9070bef --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +/** + * 数据字段信息 + */ +trait TableFieldInfo +{ + + /** + * 获取数据表字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName = ''): array + { + if ('' == $tableName) { + $tableName = $this->getTable(); + } + + return $this->connection->getTableFields($tableName); + } + + /** + * 获取详细字段类型信息 + * @access public + * @param string $tableName 数据表名称 + * @return array + */ + public function getFields(string $tableName = ''): array + { + return $this->connection->getFields($tableName ?: $this->getTable()); + } + + /** + * 获取字段类型信息 + * @access public + * @return array + */ + public function getFieldsType(): array + { + if (!empty($this->options['field_type'])) { + return $this->options['field_type']; + } + + return $this->connection->getFieldsType($this->getTable()); + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getFieldType(string $field) + { + $fieldType = $this->getFieldsType(); + + return $fieldType[$field] ?? null; + } + + /** + * 获取字段类型信息 + * @access public + * @return array + */ + public function getFieldsBindType(): array + { + $fieldType = $this->getFieldsType(); + + return array_map([$this->connection, 'getFieldBindType'], $fieldType); + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return int + */ + public function getFieldBindType(string $field): int + { + $fieldType = $this->getFieldType($field); + + return $this->connection->getFieldBindType($fieldType ?: ''); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php b/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php new file mode 100644 index 0000000..3b26654 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php @@ -0,0 +1,218 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +/** + * 时间查询支持 + */ +trait TimeFieldQuery +{ + /** + * 日期查询表达式 + * @var array + */ + protected $timeRule = [ + 'today' => ['today', 'tomorrow -1second'], + 'yesterday' => ['yesterday', 'today -1second'], + 'week' => ['this week 00:00:00', 'next week 00:00:00 -1second'], + 'last week' => ['last week 00:00:00', 'this week 00:00:00 -1second'], + 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00 -1second'], + 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00 -1second'], + 'year' => ['this year 1/1', 'next year 1/1 -1second'], + 'last year' => ['last year 1/1', 'this year 1/1 -1second'], + ]; + + /** + * 添加日期或者时间查询规则 + * @access public + * @param array $rule 时间表达式 + * @return $this + */ + public function timeRule(array $rule) + { + $this->timeRule = array_merge($this->timeRule, $rule); + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $field 日期字段名 + * @param string $op 比较运算符或者表达式 + * @param string|array $range 比较范围 + * @param string $logic AND OR + * @return $this + */ + public function whereTime(string $field, string $op, $range = null, string $logic = 'AND') + { + if (is_null($range)) { + if (isset($this->timeRule[$op])) { + $range = $this->timeRule[$op]; + } else { + $range = $op; + } + $op = is_array($range) ? 'between' : '>='; + } + + + return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true); + } + + /** + * 查询某个时间间隔数据 + * @access public + * @param string $field 日期字段名 + * @param string $start 开始时间 + * @param string $interval 时间间隔单位 day/month/year/week/hour/minute/second + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereTimeInterval(string $field, string $start, string $interval = 'day', int $step = 1, string $logic = 'AND') + { + $startTime = strtotime($start); + $endTime = strtotime(($step > 0 ? '+' : '-') . abs($step) . ' ' . $interval . (abs($step) > 1 ? 's' : ''), $startTime); + + return $this->whereTime($field, 'between', $step > 0 ? [$startTime, $endTime - 1] : [$endTime, $startTime - 1], $logic); + } + + /** + * 查询月数据 whereMonth('time_field', '2018-1') + * @access public + * @param string $field 日期字段名 + * @param string $month 月份信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereMonth(string $field, string $month = 'this month', int $step = 1, string $logic = 'AND') + { + if (in_array($month, ['this month', 'last month'])) { + $month = date('Y-m', strtotime($month)); + } + + return $this->whereTimeInterval($field, $month, 'month', $step, $logic); + } + + + + + /** + * 查询周数据 whereWeek('time_field', '2018-1-1') 从2018-1-1开始的一周数据 + * @access public + * @param string $field 日期字段名 + * @param string $week 周信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereWeek(string $field, string $week = 'this week', int $step = 1, string $logic = 'AND') + { + if (in_array($week, ['this week', 'last week'])) { + $week = date('Y-m-d', strtotime($week)); + } + + return $this->whereTimeInterval($field, $week, 'week', $step, $logic); + } + + /** + * 查询年数据 whereYear('time_field', '2018') + * @access public + * @param string $field 日期字段名 + * @param string $year 年份信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereYear(string $field, string $year = 'this year', int $step = 1, string $logic = 'AND') + { + if (in_array($year, ['this year', 'last year'])) { + $year = date('Y', strtotime($year)); + } + + return $this->whereTimeInterval($field, $year . '-1-1', 'year', $step, $logic); + } + + /** + * 查询日数据 whereDay('time_field', '2018-1-1') + * @access public + * @param string $field 日期字段名 + * @param string $day 日期信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereDay(string $field, string $day = 'today', int $step = 1, string $logic = 'AND') + { + if (in_array($day, ['today', 'yesterday'])) { + $day = date('Y-m-d', strtotime($day)); + } + + return $this->whereTimeInterval($field, $day, 'day', $step, $logic); + } + + /** + * 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15') + * @access public + * @param string $field 日期字段名 + * @param string|int $startTime 开始时间 + * @param string|int $endTime 结束时间 + * @param string $logic AND OR + * @return $this + */ + public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND') + { + return $this->whereTime($field, 'between', [$startTime, $endTime], $logic); + } + + /** + * 查询日期或者时间范围 whereNotBetweenTime('time_field', '2018-1-1','2018-1-15') + * @access public + * @param string $field 日期字段名 + * @param string|int $startTime 开始时间 + * @param string|int $endTime 结束时间 + * @return $this + */ + public function whereNotBetweenTime(string $field, $startTime, $endTime) + { + return $this->whereTime($field, '<', $startTime) + ->whereTime($field, '>', $endTime); + } + + /** + * 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time') + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereBetweenTimeField(string $startField, string $endField) + { + return $this->whereTime($startField, '<=', time()) + ->whereTime($endField, '>=', time()); + } + + /** + * 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time') + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereNotBetweenTimeField(string $startField, string $endField) + { + return $this->whereTime($startField, '>', time()) + ->whereTime($endField, '<', time(), 'OR'); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/Transaction.php b/vendor/topthink/think-orm/src/db/concern/Transaction.php new file mode 100644 index 0000000..f804ae2 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/Transaction.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use think\db\BaseQuery; + +/** + * 事务支持 + */ +trait Transaction +{ + + /** + * 执行数据库Xa事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @param array $dbs 多个查询对象或者连接对象 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transactionXa($callback, array $dbs = []) + { + $xid = uniqid('xa'); + + if (empty($dbs)) { + $dbs[] = $this->getConnection(); + } + + foreach ($dbs as $key => $db) { + if ($db instanceof BaseQuery) { + $db = $db->getConnection(); + + $dbs[$key] = $db; + } + + $db->startTransXa($xid); + } + + try { + $result = null; + if (is_callable($callback)) { + $result = call_user_func_array($callback, [$this]); + } + + foreach ($dbs as $db) { + $db->prepareXa($xid); + } + + foreach ($dbs as $db) { + $db->commitXa($xid); + } + + return $result; + } catch (\Exception | \Throwable $e) { + foreach ($dbs as $db) { + $db->rollbackXa($xid); + } + throw $e; + } + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction(callable $callback) + { + return $this->connection->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans(): void + { + $this->connection->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit(): void + { + $this->connection->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback(): void + { + $this->connection->rollback(); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/WhereQuery.php b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php new file mode 100644 index 0000000..33b0763 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php @@ -0,0 +1,540 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use Closure; +use think\db\BaseQuery; +use think\db\Raw; + +trait WhereQuery +{ + /** + * 指定AND查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function where($field, $op = null, $condition = null) + { + if ($field instanceof $this) { + $this->parseQueryWhere($field); + return $this; + } elseif (true === $field || 1 === $field) { + $this->options['where']['AND'][] = true; + return $this; + } + + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('AND', $field, $op, $condition, $param); + } + + /** + * 解析Query对象查询条件 + * @access public + * @param BaseQuery $query 查询对象 + * @return void + */ + protected function parseQueryWhere(BaseQuery $query): void + { + $this->options['where'] = $query->getOptions('where'); + + if ($query->getOptions('via')) { + $via = $query->getOptions('via'); + foreach ($this->options['where'] as $logic => &$where) { + foreach ($where as $key => &$val) { + if (is_array($val) && !strpos($val[0], '.')) { + $val[0] = $via . '.' . $val[0]; + } + } + } + } + + $this->bind($query->getBind(false)); + } + + /** + * 指定OR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereOr($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('OR', $field, $op, $condition, $param); + } + + /** + * 指定XOR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereXor($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('XOR', $field, $op, $condition, $param); + } + + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull(string $field, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NULL', null, [], true); + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull(string $field, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true); + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, string $logic = 'AND') + { + if (is_string($condition)) { + $condition = new Raw($condition); + } + + $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition]; + return $this; + } + + /** + * 指定NotExists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotExists($condition, string $logic = 'AND') + { + if (is_string($condition)) { + $condition = new Raw($condition); + } + + $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition]; + return $this; + } + + /** + * 指定In查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereIn(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true); + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true); + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true); + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true); + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true); + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true); + } + + /** + * 指定FIND_IN_SET查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereFindInSet(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'FIND IN SET', $condition, [], true); + } + + /** + * 比较两个字段 + * @access public + * @param string $field1 查询字段 + * @param string $operator 比较操作符 + * @param string $field2 比较字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND') + { + if (is_null($field2)) { + $field2 = $operator; + $operator = '='; + } + + return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true); + } + + /** + * 设置软删除字段及条件 + * @access public + * @param string $field 查询字段 + * @param mixed $condition 查询条件 + * @return $this + */ + public function useSoftDelete(string $field, $condition = null) + { + if ($field) { + $this->options['soft_delete'] = [$field, $condition]; + } + + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND') + { + if (!empty($bind)) { + $this->bindParams($where, $bind); + } + + $this->options['where'][$logic][] = [$field, 'EXP', new Raw($where)]; + + return $this; + } + + /** + * 指定字段Raw查询 + * @access public + * @param string $field 查询字段表达式 + * @param mixed $op 查询表达式 + * @param string $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND') + { + if (is_null($condition)) { + $condition = $op; + $op = '='; + } + + $this->options['where'][$logic][] = [new Raw($field), $op, $condition]; + return $this; + } + + /** + * 指定表达式查询条件 + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereRaw(string $where, array $bind = [], string $logic = 'AND') + { + if (!empty($bind)) { + $this->bindParams($where, $bind); + } + + $this->options['where'][$logic][] = new Raw($where); + + return $this; + } + + /** + * 指定表达式查询条件 OR + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function whereOrRaw(string $where, array $bind = []) + { + return $this->whereRaw($where, $bind, 'OR'); + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @param bool $strict 严格模式 + * @return $this + */ + protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false) + { + $logic = strtoupper($logic); + + if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) { + $field = $this->options['via'] . '.' . $field; + } + + if ($field instanceof Raw) { + return $this->whereRaw($field, is_array($op) ? $op : [], $logic); + } elseif ($strict) { + // 使用严格模式查询 + if ('=' == $op) { + $where = $this->whereEq($field, $condition); + } else { + $where = [$field, $op, $condition, $logic]; + } + } elseif (is_array($field)) { + // 解析数组批量查询 + return $this->parseArrayWhereItems($field, $logic); + } elseif ($field instanceof Closure) { + $where = $field; + } elseif (is_string($field)) { + if (preg_match('/[,=\<\'\"\(\s]/', $field)) { + return $this->whereRaw($field, is_array($op) ? $op : [], $logic); + } elseif (is_string($op) && strtolower($op) == 'exp') { + $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : []; + return $this->whereExp($field, $condition, $bind, $logic); + } + + $where = $this->parseWhereItem($logic, $field, $op, $condition, $param); + } + + if (!empty($where)) { + $this->options['where'][$logic][] = $where; + } + + return $this; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @return array + */ + protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array + { + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($param, $field); + $where = $param; + } elseif ($field && is_null($condition)) { + if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif ('=' === $op || is_null($op)) { + $where = [$field, 'NULL', '']; + } elseif ('<>' === $op) { + $where = [$field, 'NOTNULL', '']; + } else { + // 字段相等查询 + $where = $this->whereEq($field, $op); + } + } elseif (is_string($op) && in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { + $where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition]; + } else { + $where = $field ? [$field, $op, $condition, $param[2] ?? null] : []; + } + + return $where; + } + + /** + * 相等查询的主键处理 + * @access protected + * @param string $field 字段名 + * @param mixed $value 字段值 + * @return array + */ + protected function whereEq(string $field, $value): array + { + if ($this->getPk() == $field) { + $this->options['key'] = $value; + } + + return [$field, '=', $value]; + } + + /** + * 数组批量查询 + * @access protected + * @param array $field 批量查询 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + protected function parseArrayWhereItems(array $field, string $logic) + { + if (key($field) !== 0) { + $where = []; + foreach ($field as $key => $val) { + if ($val instanceof Raw) { + $where[] = [$key, 'exp', $val]; + } else { + $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val]; + } + } + } else { + // 数组批量查询 + $where = $field; + } + + if (!empty($where)) { + $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? + array_merge($this->options['where'][$logic], $where) : $where; + } + + return $this; + } + + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField(string $field, string $logic = 'AND') + { + $logic = strtoupper($logic); + + if (isset($this->options['where'][$logic])) { + foreach ($this->options['where'][$logic] as $key => $val) { + if (is_array($val) && $val[0] == $field) { + unset($this->options['where'][$logic][$key]); + } + } + } + + return $this; + } + + /** + * 条件查询 + * @access public + * @param mixed $condition 满足条件(支持闭包) + * @param Closure|array $query 满足条件后执行的查询表达式(闭包或数组) + * @param Closure|array $otherwise 不满足条件后执行 + * @return $this + */ + public function when($condition, $query, $otherwise = null) + { + if ($condition instanceof Closure) { + $condition = $condition($this); + } + + if ($condition) { + if ($query instanceof Closure) { + $query($this, $condition); + } elseif (is_array($query)) { + $this->where($query); + } + } elseif ($otherwise) { + if ($otherwise instanceof Closure) { + $otherwise($this, $condition); + } elseif (is_array($otherwise)) { + $this->where($otherwise); + } + } + + return $this; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Mongo.php b/vendor/topthink/think-orm/src/db/connector/Mongo.php new file mode 100644 index 0000000..4eb631b --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Mongo.php @@ -0,0 +1,1066 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\connector; + +use Closure; +use MongoDB\BSON\ObjectID; +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\AuthenticationException; +use MongoDB\Driver\Exception\BulkWriteException; +use MongoDB\Driver\Exception\ConnectionException; +use MongoDB\Driver\Exception\InvalidArgumentException; +use MongoDB\Driver\Exception\RuntimeException; +use MongoDB\Driver\Manager; +use MongoDB\Driver\Query as MongoQuery; +use MongoDB\Driver\ReadPreference; +use think\db\BaseQuery; +use think\db\builder\Mongo as Builder; +use think\db\Connection; +use think\db\ConnectionInterface; +use think\db\exception\DbException as Exception; +use think\db\Mongo as Query; + +/** + * Mongo数据库驱动 + */ +class Mongo extends Connection implements ConnectionInterface +{ + + // 查询数据类型 + protected $dbName = ''; + protected $typeMap = 'array'; + protected $mongo; // MongoDb Object + protected $cursor; // MongoCursor Object + + // 数据库连接参数配置 + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 是否是复制集 + 'is_replica_set' => false, + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 主键名 + 'pk' => '_id', + // 主键类型 + 'pk_type' => 'ObjectID', + // 数据库表前缀 + 'prefix' => '', + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 开启字段缓存 + 'fields_cache' => false, + // 监听SQL + 'trigger_sql' => true, + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否_id转换为id + 'pk_convert_id' => false, + // typeMap + 'type_map' => ['root' => 'array', 'document' => 'array'], + ]; + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + + // 创建Builder对象 + $class = $this->getBuilderClass(); + + $this->builder = new $class($this); + } + + /** + * 获取当前连接器类对应的Query类 + * @access public + * @return string + */ + public function getQueryClass(): string + { + return Query::class; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder(): Builder + { + return $this->builder; + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilderClass(): string + { + return Builder::class; + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @return Manager + * @throws InvalidArgumentException + * @throws RuntimeException + */ + public function connect(array $config = [], $linkNum = 0) + { + if (!isset($this->links[$linkNum])) { + if (empty($config)) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + + $this->dbName = $config['database']; + $this->typeMap = $config['type_map']; + + if ($config['pk_convert_id'] && '_id' == $config['pk']) { + $this->config['pk'] = 'id'; + } + + if (empty($config['dsn'])) { + $config['dsn'] = 'mongodb://' . ($config['username'] ? "{$config['username']}" : '') . ($config['password'] ? ":{$config['password']}@" : '') . $config['hostname'] . ($config['hostport'] ? ":{$config['hostport']}" : ''); + } + + $startTime = microtime(true); + + $this->links[$linkNum] = new Manager($config['dsn'], $config['params']); + + if (!empty($config['trigger_sql'])) { + // 记录数据库连接信息 + $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); + } + + } + + return $this->links[$linkNum]; + } + + /** + * 获取Mongo Manager对象 + * @access public + * @return Manager|null + */ + public function getMongo() + { + return $this->mongo ?: null; + } + + /** + * 设置/获取当前操作的database + * @access public + * @param string $db db + * @throws Exception + */ + public function db(string $db = null) + { + if (is_null($db)) { + return $this->dbName; + } else { + $this->dbName = $db; + } + } + + /** + * 执行查询但只返回Cursor对象 + * @access public + * @param BaseQuery $query 查询对象 + * @return Cursor + */ + public function cursor(BaseQuery $query) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成MongoQuery对象 + $mongoQuery = $this->builder->select($query); + + $master = $query->getOptions('master') ? true : false; + + // 执行查询操作 + return $this->getCursor($query, $mongoQuery, $master); + } + + /** + * 执行查询并返回Cursor对象 + * @access public + * @param BaseQuery $query 查询对象 + * @param MongoQuery|Closure $mongoQuery Mongo查询对象 + * @param bool $master 是否主库操作 + * @return Cursor + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function getCursor(BaseQuery $query, $mongoQuery, bool $master = false): Cursor + { + $this->initConnect($master); + $this->db->updateQueryTimes(); + + $options = $query->getOptions(); + $namespace = $options['table']; + + if (false === strpos($namespace, '.')) { + $namespace = $this->dbName . '.' . $namespace; + } + + if (!empty($this->queryStr)) { + // 记录执行指令 + $this->queryStr = 'db' . strstr($namespace, '.') . '.' . $this->queryStr; + } + + if ($mongoQuery instanceof Closure) { + $mongoQuery = $mongoQuery($query); + } + + $readPreference = $options['readPreference'] ?? null; + $this->queryStartTime = microtime(true); + + $this->cursor = $this->mongo->executeQuery($namespace, $mongoQuery, $readPreference); + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger('', $master); + } + + return $this->cursor; + } + + /** + * 执行查询 + * @access public + * @param BaseQuery $query 查询对象 + * @param MongoQuery|Closure $mongoQuery Mongo查询对象 + * @return array + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function query(BaseQuery $query, $mongoQuery): array + { + $options = $query->parseOptions(); + + if ($query->getOptions('cache')) { + // 检查查询缓存 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + if ($mongoQuery instanceof Closure) { + $mongoQuery = $mongoQuery($query); + } + + $master = $query->getOptions('master') ? true : false; + $this->getCursor($query, $mongoQuery, $master); + + $resultSet = $this->getResult($options['typeMap']); + + if (isset($cacheItem) && $resultSet) { + // 缓存数据集 + $cacheItem->set($resultSet); + $this->cacheData($cacheItem); + } + + return $resultSet; + } + + /** + * 执行写操作 + * @access public + * @param BaseQuery $query + * @param BulkWrite $bulk + * + * @return WriteResult + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function execute(BaseQuery $query, BulkWrite $bulk) + { + $this->initConnect(true); + $this->db->updateQueryTimes(); + + $options = $query->getOptions(); + + $namespace = $options['table']; + if (false === strpos($namespace, '.')) { + $namespace = $this->dbName . '.' . $namespace; + } + + if (!empty($this->queryStr)) { + // 记录执行指令 + $this->queryStr = 'db' . strstr($namespace, '.') . '.' . $this->queryStr; + } + + $writeConcern = $options['writeConcern'] ?? null; + $this->queryStartTime = microtime(true); + + $writeResult = $this->mongo->executeBulkWrite($namespace, $bulk, $writeConcern); + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger(); + } + + $this->numRows = $writeResult->getMatchedCount(); + + if ($query->getOptions('cache')) { + // 清理缓存数据 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + $tag = $cacheItem->getTag(); + + if (isset($key) && $this->cache->has($key)) { + $this->cache->delete($key); + } elseif (!empty($tag) && method_exists($this->cache, 'tag')) { + $this->cache->tag($tag)->clear(); + } + } + + return $writeResult; + } + + /** + * 执行指令 + * @access public + * @param Command $command 指令 + * @param string $dbName 当前数据库名 + * @param ReadPreference $readPreference readPreference + * @param string|array $typeMap 指定返回的typeMap + * @param bool $master 是否主库操作 + * @return array + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null, bool $master = false): array + { + $this->initConnect($master); + $this->db->updateQueryTimes(); + + $this->queryStartTime = microtime(true); + + $dbName = $dbName ?: $this->dbName; + + if (!empty($this->queryStr)) { + $this->queryStr = 'db.' . $this->queryStr; + } + + $this->cursor = $this->mongo->executeCommand($dbName, $command, $readPreference); + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger('', $master); + } + + return $this->getResult($typeMap); + } + + /** + * 获得数据集 + * @access protected + * @param string|array $typeMap 指定返回的typeMap + * @return mixed + */ + protected function getResult($typeMap = null): array + { + // 设置结果数据类型 + if (is_null($typeMap)) { + $typeMap = $this->typeMap; + } + + $typeMap = is_string($typeMap) ? ['root' => $typeMap] : $typeMap; + + $this->cursor->setTypeMap($typeMap); + + // 获取数据集 + $result = $this->cursor->toArray(); + + if ($this->getConfig('pk_convert_id')) { + // 转换ObjectID 字段 + foreach ($result as &$data) { + $this->convertObjectID($data); + } + } + + $this->numRows = count($result); + + return $result; + } + + /** + * ObjectID处理 + * @access protected + * @param array $data 数据 + * @return void + */ + protected function convertObjectID(array &$data): void + { + if (isset($data['_id']) && is_object($data['_id'])) { + $data['id'] = $data['_id']->__toString(); + unset($data['_id']); + } + } + + /** + * 数据库日志记录(仅供参考) + * @access public + * @param string $type 类型 + * @param mixed $data 数据 + * @param array $options 参数 + * @return void + */ + public function mongoLog(string $type, $data, array $options = []) + { + if (!$this->config['trigger_sql']) { + return; + } + + if (is_array($data)) { + array_walk_recursive($data, function (&$value) { + if ($value instanceof ObjectID) { + $value = $value->__toString(); + } + }); + } + + switch (strtolower($type)) { + case 'aggregate': + $this->queryStr = 'runCommand(' . ($data ? json_encode($data) : '') . ');'; + break; + case 'find': + $this->queryStr = $type . '(' . ($data ? json_encode($data) : '') . ')'; + + if (isset($options['sort'])) { + $this->queryStr .= '.sort(' . json_encode($options['sort']) . ')'; + } + + if (isset($options['skip'])) { + $this->queryStr .= '.skip(' . $options['skip'] . ')'; + } + + if (isset($options['limit'])) { + $this->queryStr .= '.limit(' . $options['limit'] . ')'; + } + + $this->queryStr .= ';'; + break; + case 'insert': + case 'remove': + $this->queryStr = $type . '(' . ($data ? json_encode($data) : '') . ');'; + break; + case 'update': + $this->queryStr = $type . '(' . json_encode($options) . ',' . json_encode($data) . ');'; + break; + case 'cmd': + $this->queryStr = $data . '(' . json_encode($options) . ');'; + break; + } + + $this->options = $options; + } + + /** + * 获取最近执行的指令 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->queryStr; + } + + /** + * 关闭数据库 + * @access public + */ + public function close() + { + $this->mongo = null; + $this->cursor = null; + $this->linkRead = null; + $this->linkWrite = null; + $this->links = []; + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect(bool $master = true): void + { + if (!empty($this->config['deploy'])) { + // 采用分布式数据库 + if ($master) { + if (!$this->linkWrite) { + $this->linkWrite = $this->multiConnect(true); + } + + $this->mongo = $this->linkWrite; + } else { + if (!$this->linkRead) { + $this->linkRead = $this->multiConnect(false); + } + + $this->mongo = $this->linkRead; + } + } elseif (!$this->mongo) { + // 默认单数据库 + $this->mongo = $this->connect(); + } + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return Manager + */ + protected function multiConnect(bool $master = false): Manager + { + $config = []; + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn'] as $name) { + $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name]; + } + + // 主服务器序号 + $m = floor(mt_rand(0, $this->config['master_num'] - 1)); + + if ($this->config['rw_separate']) { + // 主从式采用读写分离 + if ($master) // 主服务器写入 + { + if ($this->config['is_replica_set']) { + return $this->replicaSetConnect(); + } else { + $r = $m; + } + } elseif (is_numeric($this->config['slave_no'])) { + // 指定服务器读 + $r = $this->config['slave_no']; + } else { + // 读操作连接从服务器 每次随机连接的数据库 + $r = floor(mt_rand($this->config['master_num'], count($config['hostname']) - 1)); + } + } else { + // 读写操作不区分服务器 每次随机连接的数据库 + $r = floor(mt_rand(0, count($config['hostname']) - 1)); + } + + $dbConfig = []; + + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn'] as $name) { + $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0]; + } + + return $this->connect($dbConfig, $r); + } + + /** + * 创建基于复制集的连接 + * @return Manager + */ + public function replicaSetConnect(): Manager + { + $this->dbName = $this->config['database']; + $this->typeMap = $this->config['type_map']; + + $startTime = microtime(true); + + $this->config['params']['replicaSet'] = $this->config['database']; + + $manager = new Manager($this->buildUrl(), $this->config['params']); + + // 记录数据库连接信息 + if (!empty($config['trigger_sql'])) { + $this->trigger('CONNECT:ReplicaSet[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $this->config['dsn']); + } + + return $manager; + } + + /** + * 根据配置信息 生成适用于连接复制集的 URL + * @return string + */ + private function buildUrl(): string + { + $url = 'mongodb://' . ($this->config['username'] ? "{$this->config['username']}" : '') . ($this->config['password'] ? ":{$this->config['password']}@" : ''); + + $hostList = is_string($this->config['hostname']) ? explode(',', $this->config['hostname']) : $this->config['hostname']; + $portList = is_string($this->config['hostport']) ? explode(',', $this->config['hostport']) : $this->config['hostport']; + + for ($i = 0; $i < count($hostList); $i++) { + $url = $url . $hostList[$i] . ':' . $portList[0] . ','; + } + + return rtrim($url, ",") . '/'; + } + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function insert(BaseQuery $query, bool $getLastInsID = false) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + if (empty($options['data'])) { + throw new Exception('miss data to insert'); + } + + // 生成bulk对象 + $bulk = $this->builder->insert($query); + + $writeResult = $this->execute($query, $bulk); + $result = $writeResult->getInsertedCount(); + + if ($result) { + $data = $options['data']; + $lastInsId = $this->getLastInsID($query); + + if ($lastInsId) { + $pk = $query->getPk(); + $data[$pk] = $lastInsId; + } + + $query->setOption('data', $data); + + $this->db->trigger('after_insert', $query); + + if ($getLastInsID) { + return $lastInsId; + } + } + + return $result; + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @return mixed + */ + public function getLastInsID(BaseQuery $query) + { + $id = $this->builder->getLastInsID(); + + if (is_array($id)) { + array_walk($id, function (&$item, $key) { + if ($item instanceof ObjectID) { + $item = $item->__toString(); + } + }); + } elseif ($id instanceof ObjectID) { + $id = $id->__toString(); + } + + return $id; + } + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $dataSet 数据集 + * @return integer + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function insertAll(BaseQuery $query, array $dataSet = []): int + { + // 分析查询表达式 + $query->parseOptions(); + + if (!is_array(reset($dataSet))) { + return 0; + } + + // 生成bulkWrite对象 + $bulk = $this->builder->insertAll($query, $dataSet); + + $writeResult = $this->execute($query, $bulk); + + return $writeResult->getInsertedCount(); + } + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws Exception + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function update(BaseQuery $query): int + { + $query->parseOptions(); + + // 生成bulkWrite对象 + $bulk = $this->builder->update($query); + + $writeResult = $this->execute($query, $bulk); + + $result = $writeResult->getModifiedCount(); + + if ($result) { + $this->db->trigger('after_update', $query); + } + + return $result; + } + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws Exception + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function delete(BaseQuery $query): int + { + // 分析查询表达式 + $query->parseOptions(); + + // 生成bulkWrite对象 + $bulk = $this->builder->delete($query); + + // 执行操作 + $writeResult = $this->execute($query, $bulk); + + $result = $writeResult->getDeletedCount(); + + if ($result) { + $this->db->trigger('after_delete', $query); + } + + return $result; + } + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws ModelNotFoundException + * @throws DataNotFoundException + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function select(BaseQuery $query): array + { + $resultSet = $this->db->trigger('before_select', $query); + + if (!$resultSet) { + $resultSet = $this->query($query, function ($query) { + return $this->builder->select($query); + }); + } + + return $resultSet; + } + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws ModelNotFoundException + * @throws DataNotFoundException + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function find(BaseQuery $query): array + { + // 事件回调 + $result = $this->db->trigger('before_find', $query); + + if (!$result) { + // 执行查询 + $resultSet = $this->query($query, function ($query) { + return $this->builder->select($query, true); + }); + + $result = $resultSet[0] ?? []; + } + + return $result; + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null) + { + $options = $query->parseOptions(); + + if (isset($options['projection'])) { + $query->removeOption('projection'); + } + + $query->setOption('projection', (array) $field); + + if (!empty($options['cache'])) { + $cacheItem = $this->parseCache($query, $options['cache']); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + $mongoQuery = $this->builder->select($query, true); + + if (isset($options['projection'])) { + $query->setOption('projection', $options['projection']); + } else { + $query->removeOption('projection'); + } + + // 执行查询操作 + $resultSet = $this->query($query, $mongoQuery); + + if (!empty($resultSet)) { + $data = array_shift($resultSet); + $result = $data[$field]; + } else { + $result = false; + } + + if (isset($cacheItem) && false !== $result) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return false !== $result ? $result : $default; + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, string $field, string $key = ''): array + { + $options = $query->parseOptions(); + + if (isset($options['projection'])) { + $query->removeOption('projection'); + } + + if ($key && '*' != $field) { + $projection = $key . ',' . $field; + } else { + $projection = $field; + } + + $query->field($projection); + + if (!empty($options['cache'])) { + // 判断查询缓存 + $cacheItem = $this->parseCache($query, $options['cache']); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + $mongoQuery = $this->builder->select($query); + + if (isset($options['projection'])) { + $query->setOption('projection', $options['projection']); + } else { + $query->removeOption('projection'); + } + + // 执行查询操作 + $resultSet = $this->query($query, $mongoQuery); + + if (('*' == $field || strpos($field, ',')) && $key) { + $result = array_column($resultSet, null, $key); + } elseif (!empty($resultSet)) { + $result = array_column($resultSet, $field, $key); + } else { + $result = []; + } + + if (isset($cacheItem)) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return $result; + } + + /** + * 执行command + * @access public + * @param BaseQuery $query 查询对象 + * @param string|array|object $command 指令 + * @param mixed $extra 额外参数 + * @param string $db 数据库名 + * @return array + */ + public function cmd(BaseQuery $query, $command, $extra = null, string $db = ''): array + { + if (is_array($command) || is_object($command)) { + + $this->mongoLog('cmd', 'cmd', $command); + + // 直接创建Command对象 + $command = new Command($command); + } else { + // 调用Builder封装的Command对象 + $command = $this->builder->$command($query, $extra); + } + + return $this->command($command, $db); + } + + /** + * 获取数据库字段 + * @access public + * @param mixed $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName): array + { + return []; + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transaction(callable $callback) + {} + + /** + * 启动事务 + * @access public + * @return void + * @throws \PDOException + * @throws \Exception + */ + public function startTrans() + {} + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + {} + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + {} + +} diff --git a/vendor/topthink/think-orm/src/db/connector/Mysql.php b/vendor/topthink/think-orm/src/db/connector/Mysql.php new file mode 100644 index 0000000..1db3152 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Mysql.php @@ -0,0 +1,162 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * mysql数据库驱动 + */ +class Mysql extends PDOConnection +{ + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + if (!empty($config['socket'])) { + $dsn = 'mysql:unix_socket=' . $config['socket']; + } elseif (!empty($config['hostport'])) { + $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport']; + } else { + $dsn = 'mysql:host=' . $config['hostname']; + } + $dsn .= ';dbname=' . $config['database']; + + if (!empty($config['charset'])) { + $dsn .= ';charset=' . $config['charset']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + + if (false === strpos($tableName, '`')) { + if (strpos($tableName, '.')) { + $tableName = str_replace('.', '`.`', $tableName); + } + $tableName = '`' . $tableName . '`'; + } + + $sql = 'SHOW FULL COLUMNS FROM ' . $tableName; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ('' === $val['null']), // not null is empty, null is yes + 'default' => $val['default'], + 'primary' => (strtolower($val['key']) == 'pri'), + 'autoinc' => (strtolower($val['extra']) == 'auto_increment'), + 'comment' => $val['comment'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + protected function supportSavepoint(): bool + { + return true; + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa(string $xid) + { + $this->initConnect(true); + $this->linkID->execute("XA START '$xid'"); + } + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa(string $xid) + { + $this->initConnect(true); + $this->linkID->execute("XA END '$xid'"); + $this->linkID->execute("XA PREPARE '$xid'"); + } + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa(string $xid) + { + $this->initConnect(true); + $this->linkID->execute("XA COMMIT '$xid'"); + } + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa(string $xid) + { + $this->initConnect(true); + $this->linkID->execute("XA ROLLBACK '$xid'"); + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Oracle.php b/vendor/topthink/think-orm/src/db/connector/Oracle.php new file mode 100644 index 0000000..236d8bf --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Oracle.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\BaseQuery; +use think\db\PDOConnection; + +/** + * Oracle数据库驱动 + */ +class Oracle extends PDOConnection +{ + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'oci:dbname='; + + if (!empty($config['hostname'])) { + // Oracle Instant Client + $dsn .= '//' . $config['hostname'] . ($config['hostport'] ? ':' . $config['hostport'] : '') . '/'; + } + + $dsn .= $config['database']; + + if (!empty($config['charset'])) { + $dsn .= ';charset=' . $config['charset']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + $sql = "select a.column_name,data_type,DECODE (nullable, 'Y', 0, 1) notnull,data_default, DECODE (A .column_name,b.column_name,1,0) pk from all_tab_columns a,(select column_name from all_constraints c, all_cons_columns col where c.constraint_name = col.constraint_name and c.constraint_type = 'P' and c.table_name = '" . strtoupper($tableName) . "' ) b where table_name = '" . strtoupper($tableName) . "' and a.column_name = b.column_name (+)"; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['column_name']] = [ + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => $val['notnull'], + 'default' => $val['data_default'], + 'primary' => $val['pk'], + 'autoinc' => $val['pk'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息(暂时实现取得用户表信息) + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = 'select table_name from all_tables'; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @param string $sequence 自增序列名 + * @return mixed + */ + public function getLastInsID(BaseQuery $query, string $sequence = null) + { + $pdo = $this->linkID->query("select {$sequence}.currval as id from dual"); + $result = $pdo->fetchColumn(); + + return $result; + } + + protected function supportSavepoint(): bool + { + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Pgsql.php b/vendor/topthink/think-orm/src/db/connector/Pgsql.php new file mode 100644 index 0000000..fec8f84 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Pgsql.php @@ -0,0 +1,108 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends PDOConnection +{ + + /** + * 默认PDO连接参数 + * @var array + */ + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname']; + + if (!empty($config['hostport'])) { + $dsn .= ';port=' . $config['hostport']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');'; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ('' !== $val['null']), + 'default' => $val['default'], + 'primary' => !empty($val['key']), + 'autoinc' => (0 === strpos($val['extra'], 'nextval(')), + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + protected function supportSavepoint(): bool + { + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Sqlite.php b/vendor/topthink/think-orm/src/db/connector/Sqlite.php new file mode 100644 index 0000000..c664f20 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Sqlite.php @@ -0,0 +1,96 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends PDOConnection +{ + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'sqlite:' . $config['database']; + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + $sql = 'PRAGMA table_info( ' . $tableName . ' )'; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['name']] = [ + 'name' => $val['name'], + 'type' => $val['type'], + 'notnull' => 1 === $val['notnull'], + 'default' => $val['dflt_value'], + 'primary' => '1' == $val['pk'], + 'autoinc' => '1' == $val['pk'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = "SELECT name FROM sqlite_master WHERE type='table' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type='table' ORDER BY name"; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + protected function supportSavepoint(): bool + { + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php b/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php new file mode 100644 index 0000000..63b2df0 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php @@ -0,0 +1,122 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends PDOConnection +{ + /** + * 默认PDO连接参数 + * @var array + */ + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname']; + + if (!empty($config['hostport'])) { + $dsn .= ',' . $config['hostport']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + + $sql = "SELECT column_name, data_type, column_default, is_nullable + FROM information_schema.tables AS t + JOIN information_schema.columns AS c + ON t.table_catalog = c.table_catalog + AND t.table_schema = c.table_schema + AND t.table_name = c.table_name + WHERE t.table_name = '$tableName'"; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['column_name']] = [ + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes + 'default' => $val['column_default'], + 'primary' => false, + 'autoinc' => false, + ]; + } + } + + $sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'"; + $pdo = $this->linkID->query($sql); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + + if ($result) { + $info[$result['column_name']]['primary'] = true; + } + + return $this->fieldCase($info); + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + +} diff --git a/vendor/topthink/think-orm/src/db/connector/pgsql.sql b/vendor/topthink/think-orm/src/db/connector/pgsql.sql new file mode 100644 index 0000000..e1a09a3 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/pgsql.sql @@ -0,0 +1,117 @@ +CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS +$BODY$ +DECLARE + v_type varchar; +BEGIN + IF a_type='int8' THEN + v_type:='bigint'; + ELSIF a_type='int4' THEN + v_type:='integer'; + ELSIF a_type='int2' THEN + v_type:='smallint'; + ELSIF a_type='bpchar' THEN + v_type:='char'; + ELSE + v_type:=a_type; + END IF; + RETURN v_type; +END; +$BODY$ +LANGUAGE PLPGSQL; + +CREATE TYPE "public"."tablestruct" AS ( + "fields_key_name" varchar(100), + "fields_name" VARCHAR(200), + "fields_type" VARCHAR(20), + "fields_length" BIGINT, + "fields_not_null" VARCHAR(10), + "fields_default" VARCHAR(500), + "fields_comment" VARCHAR(1000) +); + +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; + v_oid oid; + v_sql varchar; + v_rec RECORD; + v_key varchar; +BEGIN + SELECT + pg_class.oid INTO v_oid + FROM + pg_class + INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name) + WHERE + pg_class.relname=a_table_name; + IF NOT FOUND THEN + RETURN; + END IF; + + v_sql=' + SELECT + pg_attribute.attname AS fields_name, + pg_attribute.attnum AS fields_index, + pgsql_type(pg_type.typname::varchar) AS fields_type, + pg_attribute.atttypmod-4 as fields_length, + CASE WHEN pg_attribute.attnotnull THEN ''not null'' + ELSE '''' + END AS fields_not_null, + pg_attrdef.adsrc AS fields_default, + pg_description.description AS fields_comment + FROM + pg_attribute + INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid + INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid + LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum + LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum + WHERE + pg_attribute.attnum > 0 + AND attisdropped <> ''t'' + AND pg_class.oid = ' || v_oid || ' + ORDER BY pg_attribute.attnum' ; + + FOR v_rec IN EXECUTE v_sql LOOP + v_ret.fields_name=v_rec.fields_name; + v_ret.fields_type=v_rec.fields_type; + IF v_rec.fields_length > 0 THEN + v_ret.fields_length:=v_rec.fields_length; + ELSE + v_ret.fields_length:=NULL; + END IF; + v_ret.fields_not_null=v_rec.fields_not_null; + v_ret.fields_default=v_rec.fields_default; + v_ret.fields_comment=v_rec.fields_comment; + SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name; + IF FOUND THEN + v_ret.fields_key_name=v_key; + ELSE + v_ret.fields_key_name=''; + END IF; + RETURN NEXT v_ret; + END LOOP; + RETURN ; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar) +IS '获得表信息'; + +---重载一个函数 +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; +BEGIN + FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP + RETURN NEXT v_ret; + END LOOP; + RETURN; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar) +IS '获得表信息'; \ No newline at end of file diff --git a/vendor/topthink/think-orm/src/db/exception/BindParamException.php b/vendor/topthink/think-orm/src/db/exception/BindParamException.php new file mode 100644 index 0000000..08bb388 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/BindParamException.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +/** + * PDO参数绑定异常 + */ +class BindParamException extends DbException +{ + + /** + * BindParamException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code + */ + public function __construct(string $message, array $config, string $sql, array $bind, int $code = 10502) + { + $this->setData('Bind Param', $bind); + parent::__construct($message, $config, $sql, $code); + } +} diff --git a/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php b/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php new file mode 100644 index 0000000..d10dd43 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +class DataNotFoundException extends DbException +{ + protected $table; + + /** + * DbException constructor. + * @access public + * @param string $message + * @param string $table + * @param array $config + */ + public function __construct(string $message, string $table = '', array $config = []) + { + $this->message = $message; + $this->table = $table; + + $this->setData('Database Config', $config); + } + + /** + * 获取数据表名 + * @access public + * @return string + */ + public function getTable() + { + return $this->table; + } +} diff --git a/vendor/topthink/think-orm/src/db/exception/DbException.php b/vendor/topthink/think-orm/src/db/exception/DbException.php new file mode 100644 index 0000000..d5bfc3f --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/DbException.php @@ -0,0 +1,81 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +use Exception; + +/** + * Database相关异常处理类 + */ +class DbException extends Exception +{ + /** + * DbException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(string $message, array $config = [], string $sql = '', int $code = 10500) + { + $this->message = $message; + $this->code = $code; + + $this->setData('Database Status', [ + 'Error Code' => $code, + 'Error Message' => $message, + 'Error SQL' => $sql, + ]); + + unset($config['username'], $config['password']); + $this->setData('Database Config', $config); + } + + /** + * 保存异常页面显示的额外Debug数据 + * @var array + */ + protected $data = []; + + /** + * 设置异常额外的Debug数据 + * 数据将会显示为下面的格式 + * + * Exception Data + * -------------------------------------------------- + * Label 1 + * key1 value1 + * key2 value2 + * Label 2 + * key1 value1 + * key2 value2 + * + * @param string $label 数据分类,用于异常页面显示 + * @param array $data 需要显示的数据,必须为关联数组 + */ + final protected function setData($label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外Debug数据 + * 主要用于输出到异常页面便于调试 + * @return array 由setData设置的Debug数据 + */ + final public function getData() + { + return $this->data; + } +} diff --git a/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php b/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php new file mode 100644 index 0000000..047e45e --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\db\exception; + +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface; + +/** + * 非法数据异常 + */ +class InvalidArgumentException extends \InvalidArgumentException implements SimpleCacheInvalidArgumentInterface +{ +} diff --git a/vendor/topthink/think-orm/src/db/exception/ModelEventException.php b/vendor/topthink/think-orm/src/db/exception/ModelEventException.php new file mode 100644 index 0000000..767bc1a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/ModelEventException.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +/** + * 模型事件异常 + */ +class ModelEventException extends DbException +{ +} diff --git a/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php b/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php new file mode 100644 index 0000000..84a1525 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +class ModelNotFoundException extends DbException +{ + protected $model; + + /** + * 构造方法 + * @access public + * @param string $message + * @param string $model + * @param array $config + */ + public function __construct(string $message, string $model = '', array $config = []) + { + $this->message = $message; + $this->model = $model; + + $this->setData('Database Config', $config); + } + + /** + * 获取模型类名 + * @access public + * @return string + */ + public function getModel() + { + return $this->model; + } + +} diff --git a/vendor/topthink/think-orm/src/db/exception/PDOException.php b/vendor/topthink/think-orm/src/db/exception/PDOException.php new file mode 100644 index 0000000..41c0d73 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/PDOException.php @@ -0,0 +1,41 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +/** + * PDO异常处理类 + * 重新封装了系统的\PDOException类 + */ +class PDOException extends DbException +{ + /** + * PDOException constructor. + * @access public + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(\PDOException $exception, array $config = [], string $sql = '', int $code = 10501) + { + $error = $exception->errorInfo; + + $this->setData('PDO Error Info', [ + 'SQLSTATE' => $error[0], + 'Driver Error Code' => isset($error[1]) ? $error[1] : 0, + 'Driver Error Message' => isset($error[2]) ? $error[2] : '', + ]); + + parent::__construct($exception->getMessage(), $config, $sql, $code); + } +} diff --git a/vendor/topthink/think-orm/src/facade/Db.php b/vendor/topthink/think-orm/src/facade/Db.php new file mode 100644 index 0000000..174a4f0 --- /dev/null +++ b/vendor/topthink/think-orm/src/facade/Db.php @@ -0,0 +1,86 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +if (class_exists('think\Facade')) { + class Facade extends \think\Facade + {} +} else { + class Facade + { + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + protected static $instance; + + /** + * 获取当前Facade对应类名 + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 创建Facade实例 + * @static + * @access protected + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + protected static function createFacade(bool $newInstance = false) + { + $class = static::getFacadeClass() ?: 'think\DbManager'; + + if (static::$alwaysNewInstance) { + $newInstance = true; + } + + if ($newInstance) { + return new $class(); + } + + if (!self::$instance) { + self::$instance = new $class(); + } + + return self::$instance; + + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([static::createFacade(), $method], $params); + } + } +} + +/** + * @see \think\DbManager + * @mixin \think\DbManager + */ +class Db extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'think\DbManager'; + } +} diff --git a/vendor/topthink/think-orm/src/model/Collection.php b/vendor/topthink/think-orm/src/model/Collection.php new file mode 100644 index 0000000..fd54272 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/Collection.php @@ -0,0 +1,250 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; +use think\Paginator; + +/** + * 模型数据集类 + */ +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param array|string $relation 关联 + * @param mixed $cache 关联缓存 + * @return $this + */ + public function load($relation, $cache = false) + { + if (!$this->isEmpty()) { + $item = current($this->items); + $item->eagerlyResultSet($this->items, (array) $relation, [], false, $cache); + } + + return $this; + } + + /** + * 删除数据集的数据 + * @access public + * @return bool + */ + public function delete(): bool + { + $this->each(function (Model $model) { + $model->delete(); + }); + + return true; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param array $allowField 允许字段 + * @return bool + */ + public function update(array $data, array $allowField = []): bool + { + $this->each(function (Model $model) use ($data, $allowField) { + if (!empty($allowField)) { + $model->allowField($allowField); + } + + $model->save($data); + }); + + return true; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @return $this + */ + public function hidden(array $hidden) + { + $this->each(function (Model $model) use ($hidden) { + $model->hidden($hidden); + }); + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @return $this + */ + public function visible(array $visible) + { + $this->each(function (Model $model) use ($visible) { + $model->visible($visible); + }); + + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @return $this + */ + public function append(array $append) + { + $this->each(function (Model $model) use ($append) { + $model->append($append); + }); + + return $this; + } + + /** + * 设置父模型 + * @access public + * @param Model $parent 父模型 + * @return $this + */ + public function setParent(Model $parent) + { + $this->each(function (Model $model) use ($parent) { + $model->setParent($parent); + }); + + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, $callback = null) + { + $this->each(function (Model $model) use ($name, $callback) { + $model->withAttribute($name, $callback); + }); + + return $this; + } + + /** + * 绑定(一对一)关联属性到当前模型 + * @access protected + * @param string $relation 关联名称 + * @param array $attrs 绑定属性 + * @return $this + * @throws Exception + */ + public function bindAttr(string $relation, array $attrs = []) + { + $this->each(function (Model $model) use ($relation, $attrs) { + $model->bindAttr($relation, $attrs); + }); + + return $this; + } + + /** + * 按指定键整理数据 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 键名 + * @return array + */ + public function dictionary($items = null, string &$indexKey = null) + { + if ($items instanceof self || $items instanceof Paginator) { + $items = $items->all(); + } + + $items = is_null($items) ? $this->items : $items; + + if ($items && empty($indexKey)) { + $indexKey = $items[0]->getPk(); + } + + if (isset($indexKey) && is_string($indexKey)) { + return array_column($items, null, $indexKey); + } + + return $items; + } + + /** + * 比较数据集,返回差集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function diff($items, string $indexKey = null) + { + if ($this->isEmpty()) { + return new static($items); + } + + $diff = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (!isset($dictionary[$item[$indexKey]])) { + $diff[] = $item; + } + } + } + + return new static($diff); + } + + /** + * 比较数据集,返回交集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function intersect($items, string $indexKey = null) + { + if ($this->isEmpty()) { + return new static([]); + } + + $intersect = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (isset($dictionary[$item[$indexKey]])) { + $intersect[] = $item; + } + } + } + + return new static($intersect); + } +} diff --git a/vendor/topthink/think-orm/src/model/Pivot.php b/vendor/topthink/think-orm/src/model/Pivot.php new file mode 100644 index 0000000..893c01b --- /dev/null +++ b/vendor/topthink/think-orm/src/model/Pivot.php @@ -0,0 +1,53 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model; + +use think\Model; + +/** + * 多对多中间表模型类 + */ +class Pivot extends Model +{ + + /** + * 父模型 + * @var Model + */ + public $parent; + + /** + * 是否时间自动写入 + * @var bool + */ + protected $autoWriteTimestamp = false; + + /** + * 架构函数 + * @access public + * @param array $data 数据 + * @param Model $parent 上级模型 + * @param string $table 中间数据表名 + */ + public function __construct(array $data = [], Model $parent = null, string $table = '') + { + $this->parent = $parent; + + if (is_null($this->name)) { + $this->name = $table; + } + + parent::__construct($data); + } + +} diff --git a/vendor/topthink/think-orm/src/model/Relation.php b/vendor/topthink/think-orm/src/model/Relation.php new file mode 100644 index 0000000..12ab8a8 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/Relation.php @@ -0,0 +1,258 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model; + +use Closure; +use ReflectionFunction; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\Model; + +/** + * 模型关联基础类 + * @package think\model + * @mixin Query + */ +abstract class Relation +{ + /** + * 父模型对象 + * @var Model + */ + protected $parent; + + /** + * 当前关联的模型类名 + * @var string + */ + protected $model; + + /** + * 关联模型查询对象 + * @var Query + */ + protected $query; + + /** + * 关联表外键 + * @var string + */ + protected $foreignKey; + + /** + * 关联表主键 + * @var string + */ + protected $localKey; + + /** + * 是否执行关联基础查询 + * @var bool + */ + protected $baseQuery; + + /** + * 是否为自关联 + * @var bool + */ + protected $selfRelation = false; + + /** + * 关联数据数量限制 + * @var int + */ + protected $withLimit; + + /** + * 关联数据字段限制 + * @var array + */ + protected $withField; + + /** + * 获取关联的所属模型 + * @access public + * @return Model + */ + public function getParent(): Model + { + return $this->parent; + } + + /** + * 获取当前的关联模型类的Query实例 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel(): Model + { + return $this->query->getModel(); + } + + /** + * 当前关联是否为自关联 + * @access public + * @return bool + */ + public function isSelfRelation(): bool + { + return $this->selfRelation; + } + + /** + * 封装关联数据集 + * @access public + * @param array $resultSet 数据集 + * @param Model $parent 父模型 + * @return mixed + */ + protected function resultSetBuild(array $resultSet, Model $parent = null) + { + return (new $this->model)->toCollection($resultSet)->setParent($parent); + } + + protected function getQueryFields(string $model) + { + $fields = $this->query->getOptions('field'); + return $this->getRelationQueryFields($fields, $model); + } + + protected function getRelationQueryFields($fields, string $model) + { + if (empty($fields) || '*' == $fields) { + return $model . '.*'; + } + + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + if (false === strpos($field, '.')) { + $field = $model . '.' . $field; + } + } + + return $fields; + } + + protected function getQueryWhere(array &$where, string $relation): void + { + foreach ($where as $key => &$val) { + if (is_string($key)) { + $where[] = [false === strpos($key, '.') ? $relation . '.' . $key : $key, '=', $val]; + unset($where[$key]); + } elseif (isset($val[0]) && false === strpos($val[0], '.')) { + $val[0] = $relation . '.' . $val[0]; + } + } + } + + /** + * 更新数据 + * @access public + * @param array $data 更新数据 + * @return integer + */ + public function update(array $data = []): int + { + return $this->query->update($data); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null): int + { + return $this->query->delete($data); + } + + /** + * 限制关联数据的数量 + * @access public + * @param int $limit 关联数量限制 + * @return $this + */ + public function withLimit(int $limit) + { + $this->withLimit = $limit; + return $this; + } + + /** + * 限制关联数据的字段 + * @access public + * @param array $field 关联字段限制 + * @return $this + */ + public function withField(array $field) + { + $this->withField = $field; + return $this; + } + + /** + * 判断闭包的参数类型 + * @access protected + * @return mixed + */ + protected function getClosureType(Closure $closure) + { + $reflect = new ReflectionFunction($closure); + $params = $reflect->getParameters(); + + if (!empty($params)) { + $type = $params[0]->getType(); + return Relation::class == $type || is_null($type) ? $this : $this->query; + } + + return $this; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + {} + + public function __call($method, $args) + { + if ($this->query) { + // 执行基础查询 + $this->baseQuery(); + + $result = call_user_func_array([$this->query, $method], $args); + + return $result === $this->query ? $this : $result; + } + + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/Attribute.php b/vendor/topthink/think-orm/src/model/concern/Attribute.php new file mode 100644 index 0000000..5c0eea7 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/Attribute.php @@ -0,0 +1,651 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use InvalidArgumentException; +use think\db\Raw; +use think\helper\Str; +use think\model\Relation; + +/** + * 模型数据处理 + */ +trait Attribute +{ + /** + * 数据表主键 复合主键使用数组定义 + * @var string|array + */ + protected $pk = 'id'; + + /** + * 数据表字段信息 留空则自动获取 + * @var array + */ + protected $schema = []; + + /** + * 当前允许写入的字段 + * @var array + */ + protected $field = []; + + /** + * 字段自动类型转换 + * @var array + */ + protected $type = []; + + /** + * 数据表废弃字段 + * @var array + */ + protected $disuse = []; + + /** + * 数据表只读字段 + * @var array + */ + protected $readonly = []; + + /** + * 当前模型数据 + * @var array + */ + private $data = []; + + /** + * 原始数据 + * @var array + */ + private $origin = []; + + /** + * JSON数据表字段 + * @var array + */ + protected $json = []; + + /** + * JSON数据表字段类型 + * @var array + */ + protected $jsonType = []; + + /** + * JSON数据取出是否需要转换为数组 + * @var bool + */ + protected $jsonAssoc = false; + + /** + * 是否严格字段大小写 + * @var bool + */ + protected $strict = true; + + /** + * 修改器执行记录 + * @var array + */ + private $set = []; + + /** + * 动态获取器 + * @var array + */ + private $withAttr = []; + + /** + * 获取模型对象的主键 + * @access public + * @return string|array + */ + public function getPk() + { + return $this->pk; + } + + /** + * 判断一个字段名是否为主键字段 + * @access public + * @param string $key 名称 + * @return bool + */ + protected function isPk(string $key): bool + { + $pk = $this->getPk(); + + if (is_string($pk) && $pk == $key) { + return true; + } elseif (is_array($pk) && in_array($key, $pk)) { + return true; + } + + return false; + } + + /** + * 获取模型对象的主键值 + * @access public + * @return mixed + */ + public function getKey() + { + $pk = $this->getPk(); + + if (is_string($pk) && array_key_exists($pk, $this->data)) { + return $this->data[$pk]; + } + + return; + } + + /** + * 设置允许写入的字段 + * @access public + * @param array $field 允许写入的字段 + * @return $this + */ + public function allowField(array $field) + { + $this->field = $field; + + return $this; + } + + /** + * 设置只读字段 + * @access public + * @param array $field 只读字段 + * @return $this + */ + public function readOnly(array $field) + { + $this->readonly = $field; + + return $this; + } + + /** + * 获取实际的字段名 + * @access protected + * @param string $name 字段名 + * @return string + */ + protected function getRealFieldName(string $name): string + { + return $this->strict ? $name : Str::snake($name); + } + + /** + * 设置数据对象值 + * @access public + * @param array $data 数据 + * @param bool $set 是否调用修改器 + * @param array $allow 允许的字段名 + * @return $this + */ + public function data(array $data, bool $set = false, array $allow = []) + { + // 清空数据 + $this->data = []; + + // 废弃字段 + foreach ($this->disuse as $key) { + if (array_key_exists($key, $data)) { + unset($data[$key]); + } + } + + if (!empty($allow)) { + $result = []; + foreach ($allow as $name) { + if (isset($data[$name])) { + $result[$name] = $data[$name]; + } + } + $data = $result; + } + + if ($set) { + // 数据对象赋值 + $this->setAttrs($data); + } else { + $this->data = $data; + } + + return $this; + } + + /** + * 批量追加数据对象值 + * @access public + * @param array $data 数据 + * @param bool $set 是否需要进行数据处理 + * @return $this + */ + public function appendData(array $data, bool $set = false) + { + if ($set) { + $this->setAttrs($data); + } else { + $this->data = array_merge($this->data, $data); + } + + return $this; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回null + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + */ + public function getOrigin(string $name = null) + { + if (is_null($name)) { + return $this->origin; + } + + return array_key_exists($name, $this->origin) ? $this->origin[$name] : null; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回false + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + * @throws InvalidArgumentException + */ + public function getData(string $name = null) + { + if (is_null($name)) { + return $this->data; + } + + $fieldName = $this->getRealFieldName($name); + + if (array_key_exists($fieldName, $this->data)) { + return $this->data[$fieldName]; + } elseif (array_key_exists($fieldName, $this->relation)) { + return $this->relation[$fieldName]; + } + + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); + } + + /** + * 获取变化的数据 并排除只读数据 + * @access public + * @return array + */ + public function getChangedData(): array + { + $data = $this->force ? $this->data : array_udiff_assoc($this->data, $this->origin, function ($a, $b) { + if ((empty($a) || empty($b)) && $a !== $b) { + return 1; + } + + return is_object($a) || $a != $b ? 1 : 0; + }); + + // 只读字段不允许更新 + foreach ($this->readonly as $key => $field) { + if (isset($data[$field])) { + unset($data[$field]); + } + } + + return $data; + } + + /** + * 直接设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 值 + * @return void + */ + public function set(string $name, $value): void + { + $name = $this->getRealFieldName($name); + + $this->data[$name] = $value; + } + + /** + * 通过修改器 批量设置数据对象值 + * @access public + * @param array $data 数据 + * @return void + */ + public function setAttrs(array $data): void + { + // 进行数据处理 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } + + /** + * 通过修改器 设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return void + */ + public function setAttr(string $name, $value, array $data = []): void + { + $name = $this->getRealFieldName($name); + + if (isset($this->set[$name])) { + return; + } + + if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { + // 自动写入的时间戳字段 + $value = $this->autoWriteTimestamp(); + } else { + // 检测修改器 + $method = 'set' . Str::studly($name) . 'Attr'; + + if (method_exists($this, $method)) { + $array = $this->data; + + $value = $this->$method($value, array_merge($this->data, $data)); + + $this->set[$name] = true; + if (is_null($value) && $array !== $this->data) { + return; + } + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->writeTransform($value, $this->type[$name]); + } + } + + // 设置数据对象属性 + $this->data[$name] = $value; + } + + /** + * 数据写入 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function writeTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if ($value instanceof Raw) { + return $value; + } + + if (is_array($type)) { + [$type, $param] = $type; + } elseif (strpos($type, ':')) { + [$type, $param] = explode(':', $type, 2); + } + + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_numeric($value)) { + $value = strtotime($value); + } + break; + case 'datetime': + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime('Y-m-d H:i:s.u', $value); + break; + case 'object': + if (is_object($value)) { + $value = json_encode($value, JSON_FORCE_OBJECT); + } + break; + case 'array': + $value = (array) $value; + case 'json': + $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE; + $value = json_encode($value, $option); + break; + case 'serialize': + $value = serialize($value); + break; + default: + if (is_object($value) && false !== strpos($type, '\\') && method_exists($value, '__toString')) { + // 对象类型 + $value = $value->__toString(); + } + } + + return $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + * @throws InvalidArgumentException + */ + public function getAttr(string $name) + { + try { + $relation = false; + $value = $this->getData($name); + } catch (InvalidArgumentException $e) { + $relation = $this->isRelationAttr($name); + $value = null; + } + + return $this->getValue($name, $value, $relation); + } + + /** + * 获取经过获取器处理后的数据对象的值 + * @access protected + * @param string $name 字段名称 + * @param mixed $value 字段值 + * @param bool|string $relation 是否为关联属性或者关联名 + * @return mixed + * @throws InvalidArgumentException + */ + protected function getValue(string $name, $value, $relation = false) + { + // 检测属性获取器 + $fieldName = $this->getRealFieldName($name); + $method = 'get' . Str::studly($name) . 'Attr'; + + if (isset($this->withAttr[$fieldName])) { + if ($relation) { + $value = $this->getRelationValue($relation); + } + + if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) { + $value = $this->getJsonValue($fieldName, $value); + } else { + $closure = $this->withAttr[$fieldName]; + $value = $closure($value, $this->data); + } + } elseif (method_exists($this, $method)) { + if ($relation) { + $value = $this->getRelationValue($relation); + } + + $value = $this->$method($value, $this->data); + } elseif (isset($this->type[$fieldName])) { + // 类型转换 + $value = $this->readTransform($value, $this->type[$fieldName]); + } elseif ($this->autoWriteTimestamp && in_array($fieldName, [$this->createTime, $this->updateTime])) { + $value = $this->getTimestampValue($value); + } elseif ($relation) { + $value = $this->getRelationValue($relation); + // 保存关联对象值 + $this->relation[$name] = $value; + } + + return $value; + } + + /** + * 获取JSON字段属性值 + * @access protected + * @param string $name 属性名 + * @param mixed $value JSON数据 + * @return mixed + */ + protected function getJsonValue($name, $value) + { + foreach ($this->withAttr[$name] as $key => $closure) { + if ($this->jsonAssoc) { + $value[$key] = $closure($value[$key], $value); + } else { + $value->$key = $closure($value->$key, $value); + } + } + + return $value; + } + + /** + * 获取关联属性值 + * @access protected + * @param string $relation 关联名 + * @return mixed + */ + protected function getRelationValue(string $relation) + { + $modelRelation = $this->$relation(); + + return $modelRelation instanceof Relation ? $this->getRelationData($modelRelation) : null; + } + + /** + * 数据读取 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function readTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + [$type, $param] = $type; + } elseif (strpos($type, ':')) { + [$type, $param] = explode(':', $type, 2); + } + + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format, $value, true); + } + break; + case 'datetime': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format, $value); + } + break; + case 'json': + $value = json_decode($value, true); + break; + case 'array': + $value = empty($value) ? [] : json_decode($value, true); + break; + case 'object': + $value = empty($value) ? new \stdClass() : json_decode($value); + break; + case 'serialize': + try { + $value = unserialize($value); + } catch (\Exception $e) { + $value = null; + } + break; + default: + if (false !== strpos($type, '\\')) { + // 对象类型 + $value = new $type($value); + } + } + + return $value; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttribute($name, callable $callback = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + $this->withAttribute($key, $val); + } + } else { + $name = $this->getRealFieldName($name); + + if (strpos($name, '.')) { + [$name, $key] = explode('.', $name); + + $this->withAttr[$name][$key] = $callback; + } else { + $this->withAttr[$name] = $callback; + } + } + + return $this; + } + +} diff --git a/vendor/topthink/think-orm/src/model/concern/Conversion.php b/vendor/topthink/think-orm/src/model/concern/Conversion.php new file mode 100644 index 0000000..3574bae --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/Conversion.php @@ -0,0 +1,285 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\Collection; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Collection as ModelCollection; +use think\model\relation\OneToOne; + +/** + * 模型数据转换处理 + */ +trait Conversion +{ + /** + * 数据输出显示的属性 + * @var array + */ + protected $visible = []; + + /** + * 数据输出隐藏的属性 + * @var array + */ + protected $hidden = []; + + /** + * 数据输出需要追加的属性 + * @var array + */ + protected $append = []; + + /** + * 数据集对象名 + * @var string + */ + protected $resultSetType; + + /** + * 设置需要附加的输出属性 + * @access public + * @param array $append 属性列表 + * @return $this + */ + public function append(array $append = []) + { + $this->append = $append; + + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $attr 关联属性 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr(string $attr, array $append) + { + $relation = Str::camel($attr); + + if (isset($this->relation[$relation])) { + $model = $this->relation[$relation]; + } else { + $model = $this->getRelationData($this->$relation()); + } + + if ($model instanceof Model) { + foreach ($append as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($this->data[$key])) { + throw new Exception('bind attr has exists:' . $key); + } + + $this->data[$key] = $model->$attr; + } + } + + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @return $this + */ + public function hidden(array $hidden = []) + { + $this->hidden = $hidden; + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @return $this + */ + public function visible(array $visible = []) + { + $this->visible = $visible; + + return $this; + } + + /** + * 转换当前模型对象为数组 + * @access public + * @return array + */ + public function toArray(): array + { + $item = []; + $hasVisible = false; + + + foreach ($this->visible as $key => $val) { + if (is_string($val)) { + if (strpos($val, '.')) { + [$relation, $name] = explode('.', $val); + $this->visible[$relation][] = $name; + } else { + $this->visible[$val] = true; + $hasVisible = true; + } + unset($this->visible[$key]); + } + } + + foreach ($this->hidden as $key => $val) { + if (is_string($val)) { + if (strpos($val, '.')) { + [$relation, $name] = explode('.', $val); + $this->hidden[$relation][] = $name; + } else { + $this->hidden[$val] = true; + } + unset($this->hidden[$key]); + } + } + + // 合并关联数据 + $data = array_merge($this->data, $this->relation); + + foreach ($data as $key => $val) { + if ($val instanceof Model || $val instanceof ModelCollection) { + // 关联模型对象 + if (isset($this->visible[$key]) && is_array($this->visible[$key])) { + $val->visible($this->visible[$key]); + } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) { + $val->hidden($this->hidden[$key]); + } + // 关联模型对象 + if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) { + $item[$key] = $val->toArray(); + } + } elseif (isset($this->visible[$key])) { + $item[$key] = $this->getAttr($key); + } elseif (!isset($this->hidden[$key]) && !$hasVisible) { + $item[$key] = $this->getAttr($key); + } + } + + // 追加属性(必须定义获取器) + foreach ($this->append as $key => $name) { + $this->appendAttrToArray($item, $key, $name); + } + + return $item; + } + + protected function appendAttrToArray(array &$item, $key, $name) + { + if (is_array($name)) { + + + // 追加关联对象属性 + $relation = $this->getRelation($key, true); + $item[$key] = $relation ? $relation->append($name) + ->toArray() : []; + } elseif (strpos($name, '.')) { + + + [$key, $attr] = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getRelation($key, true); + $item[$key] = $relation ? $relation->append([$attr]) + ->toArray() : []; + } else { + + $value = $this->getAttr($name); + + $item[$name] = $value; + + $this->getBindAttr($name, $value, $item); + } + } + + protected function getBindAttr(string $name, $value, array &$item = []) + { + $relation = $this->isRelationAttr($name); + if (!$relation) { + return false; + } + + $modelRelation = $this->$relation(); + + if ($modelRelation instanceof OneToOne) { + $bindAttr = $modelRelation->getBindAttr(); + + if (!empty($bindAttr)) { + unset($item[$name]); + } + + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + + if (isset($item[$key])) { + throw new Exception('bind attr has exists:' . $key); + } + + $item[$key] = $value ? $value->getAttr($attr) : null; + } + } + } + + /** + * 转换当前模型对象为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson(int $options = JSON_UNESCAPED_UNICODE): string + { + return json_encode($this->toArray(), $options); + } + + public function __toString() + { + return $this->toJson(); + } + + // JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换数据集为数据集对象 + * @access public + * @param array|Collection $collection 数据集 + * @param string $resultSetType 数据集类 + * @return Collection + */ + public function toCollection(iterable $collection = [], string $resultSetType = null): Collection + { + $resultSetType = $resultSetType ?: $this->resultSetType; + + if ($resultSetType && false !== strpos($resultSetType, '\\')) { + $collection = new $resultSetType($collection); + } else { + $collection = new ModelCollection($collection); + } + + return $collection; + } + +} diff --git a/vendor/topthink/think-orm/src/model/concern/ModelEvent.php b/vendor/topthink/think-orm/src/model/concern/ModelEvent.php new file mode 100644 index 0000000..f560379 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/ModelEvent.php @@ -0,0 +1,88 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\exception\ModelEventException; +use think\helper\Str; + +/** + * 模型事件处理 + */ +trait ModelEvent +{ + + /** + * Event对象 + * @var object + */ + protected static $event; + + /** + * 是否需要事件响应 + * @var bool + */ + protected $withEvent = true; + + /** + * 设置Event对象 + * @access public + * @param object $event Event对象 + * @return void + */ + public static function setEvent($event) + { + self::$event = $event; + } + + /** + * 当前操作的事件响应 + * @access protected + * @param bool $event 是否需要事件响应 + * @return $this + */ + public function withEvent(bool $event) + { + $this->withEvent = $event; + return $this; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @return bool + */ + protected function trigger(string $event): bool + { + if (!$this->withEvent) { + return true; + } + + $call = 'on' . Str::studly($event); + + try { + if (method_exists(static::class, $call)) { + $result = call_user_func([static::class, $call], $this); + } elseif (is_object(self::$event) && method_exists(self::$event, 'trigger')) { + $result = self::$event->trigger(static::class . '.' . $event, $this); + $result = empty($result) ? true : end($result); + } else { + $result = true; + } + + return false === $result ? false : true; + } catch (ModelEventException $e) { + return false; + } + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/OptimLock.php b/vendor/topthink/think-orm/src/model/concern/OptimLock.php new file mode 100644 index 0000000..5e61318 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/OptimLock.php @@ -0,0 +1,85 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\exception\DbException as Exception; + +/** + * 乐观锁 + */ +trait OptimLock +{ + protected function getOptimLockField() + { + return property_exists($this, 'optimLock') && isset($this->optimLock) ? $this->optimLock : 'lock_version'; + } + + /** + * 数据检查 + * @access protected + * @return void + */ + protected function checkData(): void + { + $this->isExists() ? $this->updateLockVersion() : $this->recordLockVersion(); + } + + /** + * 记录乐观锁 + * @access protected + * @return void + */ + protected function recordLockVersion(): void + { + $optimLock = $this->getOptimLockField(); + + if ($optimLock) { + $this->set($optimLock, 0); + } + } + + /** + * 更新乐观锁 + * @access protected + * @return void + */ + protected function updateLockVersion(): void + { + $optimLock = $this->getOptimLockField(); + + if ($optimLock && $lockVer = $this->getOrigin($optimLock)) { + // 更新乐观锁 + $this->set($optimLock, $lockVer + 1); + } + } + + public function getWhere() + { + $where = parent::getWhere(); + $optimLock = $this->getOptimLockField(); + + if ($optimLock && $lockVer = $this->getOrigin($optimLock)) { + $where[] = [$optimLock, '=', $lockVer]; + } + + return $where; + } + + protected function checkResult($result): void + { + if (!$result) { + throw new Exception('record has update'); + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/concern/RelationShip.php b/vendor/topthink/think-orm/src/model/concern/RelationShip.php new file mode 100644 index 0000000..7b0d4a7 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/RelationShip.php @@ -0,0 +1,781 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; +use think\model\relation\BelongsTo; +use think\model\relation\BelongsToMany; +use think\model\relation\HasMany; +use think\model\relation\HasManyThrough; +use think\model\relation\HasOne; +use think\model\relation\HasOneThrough; +use think\model\relation\MorphMany; +use think\model\relation\MorphOne; +use think\model\relation\MorphTo; +use think\model\relation\OneToOne; + +/** + * 模型关联处理 + */ +trait RelationShip +{ + /** + * 父关联模型对象 + * @var object + */ + private $parent; + + /** + * 模型关联数据 + * @var array + */ + private $relation = []; + + /** + * 关联写入定义信息 + * @var array + */ + private $together = []; + + /** + * 关联自动写入信息 + * @var array + */ + protected $relationWrite = []; + + /** + * 设置父关联对象 + * @access public + * @param Model $model 模型对象 + * @return $this + */ + public function setParent(Model $model) + { + $this->parent = $model; + + return $this; + } + + /** + * 获取父关联对象 + * @access public + * @return Model + */ + public function getParent(): Model + { + return $this->parent; + } + + /** + * 获取当前模型的关联模型数据 + * @access public + * @param string $name 关联方法名 + * @param bool $auto 不存在是否自动获取 + * @return mixed + */ + public function getRelation(string $name = null, bool $auto = false) + { + if (is_null($name)) { + return $this->relation; + } + + if (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } elseif ($auto) { + $relation = Str::camel($name); + return $this->getRelationValue($relation); + } + } + + /** + * 设置关联数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return $this + */ + public function setRelation(string $name, $value, array $data = []) + { + // 检测修改器 + $method = 'set' . Str::studly($name) . 'Attr'; + + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data)); + } + + $this->relation[$this->getRealFieldName($name)] = $value; + + return $this; + } + + /** + * 查询当前模型的关联数据 + * @access public + * @param array $relations 关联名 + * @param array $withRelationAttr 关联获取器 + * @return void + */ + public function relationQuery(array $relations, array $withRelationAttr = []): void + { + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + + if ($relation instanceof Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + [$relation, $subRelation] = explode('.', $relation, 2); + } + + $method = Str::camel($relation); + $relationName = Str::snake($relation); + + $relationResult = $this->$method(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + $this->relation[$relation] = $relationResult->getRelation($subRelation, $closure); + } + } + + /** + * 关联数据写入 + * @access public + * @param array $relation 关联 + * @return $this + */ + public function together(array $relation) + { + $this->together = $relation; + + $this->checkAutoRelationWrite(); + + return $this; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public static function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + return (new static()) + ->$relation() + ->has($operator, $count, $id, $joinType, $query); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public static function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '', Query $query = null): Query + { + return (new static()) + ->$relation() + ->hasWhere($where, $fields, $joinType, $query); + } + + /** + * 预载入关联查询 JOIN方式 + * @access public + * @param Query $query Query对象 + * @param string $relation 关联方法名 + * @param mixed $field 字段 + * @param string $joinType JOIN类型 + * @param Closure $closure 闭包 + * @param bool $first + * @return bool + */ + public function eagerly(Query $query, string $relation, $field, string $joinType = '', Closure $closure = null, bool $first = false): bool + { + $relation = Str::camel($relation); + $class = $this->$relation(); + + if ($class instanceof OneToOne) { + $class->eagerly($query, $relation, $field, $joinType, $closure, $first); + return true; + } else { + return false; + } + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param array $withRelationAttr 关联获取器 + * @param bool $join 是否为JOIN方式 + * @param mixed $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void + { + foreach ($relations as $key => $relation) { + $subRelation = []; + $closure = null; + + if ($relation instanceof Closure) { + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + [$relation, $subRelation] = explode('.', $relation, 2); + + $subRelation = [$subRelation]; + } + + $relationName = $relation; + $relation = Str::camel($relation); + + $relationResult = $this->$relation(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + if (is_scalar($cache)) { + $relationCache = [$cache]; + } else { + $relationCache = $cache[$relationName] ?? $cache; + } + + $relationResult->eagerlyResultSet($resultSet, $relationName, $subRelation, $closure, $relationCache, $join); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param array $relations 关联 + * @param array $withRelationAttr 关联获取器 + * @param bool $join 是否为JOIN方式 + * @param mixed $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void + { + foreach ($relations as $key => $relation) { + $subRelation = []; + $closure = null; + + if ($relation instanceof Closure) { + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + [$relation, $subRelation] = explode('.', $relation, 2); + + $subRelation = [$subRelation]; + } + + $relationName = $relation; + $relation = Str::camel($relation); + + $relationResult = $this->$relation(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + if (is_scalar($cache)) { + $relationCache = [$cache]; + } else { + $relationCache = $cache[$relationName] ?? []; + } + + $relationResult->eagerlyResult($result, $relationName, $subRelation, $closure, $relationCache, $join); + } + } + + /** + * 绑定(一对一)关联属性到当前模型 + * @access protected + * @param string $relation 关联名称 + * @param array $attrs 绑定属性 + * @return $this + * @throws Exception + */ + public function bindAttr(string $relation, array $attrs = []) + { + $relation = $this->getRelation($relation); + + foreach ($attrs as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $this->getOrigin($key); + + if (!is_null($value)) { + throw new Exception('bind attr has exists:' . $key); + } + + $this->set($key, $relation ? $relation->$attr : null); + } + + return $this; + } + + /** + * 关联统计 + * @access public + * @param Query $query 查询对象 + * @param array $relations 关联名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param bool $useSubQuery 子查询 + * @return void + */ + public function relationCount(Query $query, array $relations, string $aggregate = 'sum', string $field = '*', bool $useSubQuery = true): void + { + foreach ($relations as $key => $relation) { + $closure = $name = null; + + if ($relation instanceof Closure) { + $closure = $relation; + $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; + } + + $relation = Str::camel($relation); + + if ($useSubQuery) { + $count = $this->$relation()->getRelationCountQuery($closure, $aggregate, $field, $name); + } else { + $count = $this->$relation()->relationCount($this, $closure, $aggregate, $field, $name); + } + + if (empty($name)) { + $name = Str::snake($relation) . '_' . $aggregate; + } + + if ($useSubQuery) { + $query->field(['(' . $count . ')' => $name]); + } else { + $this->setAttr($name, $count); + } + } + } + + /** + * HAS ONE 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasOne + */ + public function hasOne(string $model, string $foreignKey = '', string $localKey = ''): HasOne + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasOne($this, $model, $foreignKey, $localKey); + } + + /** + * BELONGS TO 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @return BelongsTo + */ + public function belongsTo(string $model, string $foreignKey = '', string $localKey = ''): BelongsTo + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey((new $model)->getName()); + $localKey = $localKey ?: (new $model)->getPk(); + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $relation = Str::snake($trace[1]['function']); + + return new BelongsTo($this, $model, $foreignKey, $localKey, $relation); + } + + /** + * HAS MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasMany + */ + public function hasMany(string $model, string $foreignKey = '', string $localKey = ''): HasMany + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasMany($this, $model, $foreignKey, $localKey); + } + + /** + * HAS MANY 远程关联定义 + * @access public + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 + * @param string $throughPk 中间表主键 + * @return HasManyThrough + */ + public function hasManyThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasManyThrough + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName()); + $throughPk = $throughPk ?: (new $through)->getPk(); + + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk); + } + + /** + * HAS ONE 远程关联定义 + * @access public + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 + * @param string $throughPk 中间表主键 + * @return HasOneThrough + */ + public function hasOneThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasOneThrough + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName()); + $throughPk = $throughPk ?: (new $through)->getPk(); + + return new HasOneThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk); + } + + /** + * BELONGS TO MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $middle 中间表/模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型关联键 + * @return BelongsToMany + */ + public function belongsToMany(string $model, string $middle = '', string $foreignKey = '', string $localKey = ''): BelongsToMany + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $name = Str::snake(class_basename($model)); + $middle = $middle ?: Str::snake($this->name) . '_' . $name; + $foreignKey = $foreignKey ?: $name . '_id'; + $localKey = $localKey ?: $this->getForeignKey($this->name); + + return new BelongsToMany($this, $model, $middle, $foreignKey, $localKey); + } + + /** + * MORPH One 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphOne + */ + public function morphOne(string $model, $morph = null, string $type = ''): MorphOne + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $morph = Str::snake($trace[1]['function']); + } + + if (is_array($morph)) { + [$morphType, $foreignKey] = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + $type = $type ?: get_class($this); + + return new MorphOne($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany(string $model, $morph = null, string $type = ''): MorphMany + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $morph = Str::snake($trace[1]['function']); + } + + $type = $type ?: get_class($this); + + if (is_array($morph)) { + [$morphType, $foreignKey] = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, array $alias = []): MorphTo + { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $relation = Str::snake($trace[1]['function']); + + if (is_null($morph)) { + $morph = $relation; + } + + // 记录当前关联信息 + if (is_array($morph)) { + [$morphType, $foreignKey] = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel(string $model): string + { + if (false === strpos($model, '\\')) { + $path = explode('\\', static::class); + array_pop($path); + array_push($path, Str::studly($model)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 获取模型的默认外键名 + * @access protected + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey(string $name): string + { + if (strpos($name, '\\')) { + $name = class_basename($name); + } + + return Str::snake($name) . '_id'; + } + + /** + * 检查属性是否为关联属性 如果是则返回关联方法名 + * @access protected + * @param string $attr 关联属性名 + * @return string|false + */ + protected function isRelationAttr(string $attr) + { + $relation = Str::camel($attr); + + if (method_exists($this, $relation) && !method_exists('think\Model', $relation)) { + return $relation; + } + + return false; + } + + /** + * 智能获取关联模型数据 + * @access protected + * @param Relation $modelRelation 模型关联对象 + * @return mixed + */ + protected function getRelationData(Relation $modelRelation) + { + if ($this->parent && !$modelRelation->isSelfRelation() + && get_class($this->parent) == get_class($modelRelation->getModel())) { + return $this->parent; + } + + // 获取关联数据 + return $modelRelation->getRelation(); + } + + /** + * 关联数据自动写入检查 + * @access protected + * @return void + */ + protected function checkAutoRelationWrite(): void + { + foreach ($this->together as $key => $name) { + if (is_array($name)) { + if (key($name) === 0) { + $this->relationWrite[$key] = []; + // 绑定关联属性 + foreach ($name as $val) { + if (isset($this->data[$val])) { + $this->relationWrite[$key][$val] = $this->data[$val]; + } + } + } else { + // 直接传入关联数据 + $this->relationWrite[$key] = $name; + } + } elseif (isset($this->relation[$name])) { + $this->relationWrite[$name] = $this->relation[$name]; + } elseif (isset($this->data[$name])) { + $this->relationWrite[$name] = $this->data[$name]; + unset($this->data[$name]); + } + } + } + + /** + * 自动关联数据更新(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationUpdate(): void + { + foreach ($this->relationWrite as $name => $val) { + if ($val instanceof Model) { + $val->exists(true)->save(); + } else { + $model = $this->getRelation($name, true); + + if ($model instanceof Model) { + $model->exists(true)->save($val); + } + } + } + } + + /** + * 自动关联数据写入(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationInsert(): void + { + foreach ($this->relationWrite as $name => $val) { + $method = Str::camel($name); + $this->$method()->save($val); + } + } + + /** + * 自动关联数据删除(支持一对一及一对多关联) + * @access protected + * @return void + */ + protected function autoRelationDelete(): void + { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $result = $this->getRelation($name, true); + + if ($result instanceof Model) { + $result->delete(); + } elseif ($result instanceof Collection) { + foreach ($result as $model) { + $model->delete(); + } + } + } + } + + /** + * 移除当前模型的关联属性 + * @access public + * @return $this + */ + public function removeRelation() + { + $this->relation = []; + return $this; + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/SoftDelete.php b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php new file mode 100644 index 0000000..7357bc5 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php @@ -0,0 +1,246 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\BaseQuery as Query; + +/** + * 数据软删除 + */ +trait SoftDelete +{ + /** + * 是否包含软删除数据 + * @var bool + */ + protected $withTrashed = false; + + /** + * 判断当前实例是否被软删除 + * @access public + * @return bool + */ + public function trashed(): bool + { + $field = $this->getDeleteTimeField(); + + if ($field && !empty($this->getOrigin($field))) { + return true; + } + + return false; + } + + /** + * 查询软删除数据 + * @access public + * @return Query + */ + public static function withTrashed(): Query + { + $model = new static(); + + return $model->withTrashedData(true)->db(); + } + + /** + * 是否包含软删除数据 + * @access protected + * @param bool $withTrashed 是否包含软删除数据 + * @return $this + */ + protected function withTrashedData(bool $withTrashed) + { + $this->withTrashed = $withTrashed; + return $this; + } + + /** + * 只查询软删除数据 + * @access public + * @return Query + */ + public static function onlyTrashed(): Query + { + $model = new static(); + $field = $model->getDeleteTimeField(true); + + if ($field) { + return $model + ->db() + ->useSoftDelete($field, $model->getWithTrashedExp()); + } + + return $model->db(); + } + + /** + * 获取软删除数据的查询条件 + * @access protected + * @return array + */ + protected function getWithTrashedExp(): array + { + return is_null($this->defaultSoftDelete) ? ['notnull', ''] : ['<>', $this->defaultSoftDelete]; + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete(): bool + { + if (!$this->isExists() || $this->isEmpty() || false === $this->trigger('BeforeDelete')) { + return false; + } + + $name = $this->getDeleteTimeField(); + + if ($name && !$this->isForce()) { + // 软删除 + $this->set($name, $this->autoWriteTimestamp($name)); + + $result = $this->exists()->withEvent(false)->save(); + + $this->withEvent(true); + } else { + // 读取更新条件 + $where = $this->getWhere(); + + // 删除当前模型数据 + $result = $this->db() + ->where($where) + ->removeOption('soft_delete') + ->delete(); + + $this->lazySave(false); + } + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + + $this->trigger('AfterDelete'); + + $this->exists(false); + + return true; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @param bool $force 是否强制删除 + * @return bool + */ + public static function destroy($data, bool $force = false): bool + { + // 包含软删除数据 + $query = (new static())->db(false); + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $query]); + $data = null; + } elseif (is_null($data)) { + return false; + } + + $resultSet = $query->select($data); + + foreach ($resultSet as $result) { + $result->force($force)->delete(); + } + + return true; + } + + /** + * 恢复被软删除的记录 + * @access public + * @param array $where 更新条件 + * @return bool + */ + public function restore($where = []): bool + { + $name = $this->getDeleteTimeField(); + + if (!$name || false === $this->trigger('BeforeRestore')) { + return false; + } + + if (empty($where)) { + $pk = $this->getPk(); + if (is_string($pk)) { + $where[] = [$pk, '=', $this->getData($pk)]; + } + } + + // 恢复删除 + $this->db(false) + ->where($where) + ->useSoftDelete($name, $this->getWithTrashedExp()) + ->update([$name => $this->defaultSoftDelete]); + + $this->trigger('AfterRestore'); + + return true; + } + + /** + * 获取软删除字段 + * @access protected + * @param bool $read 是否查询操作 写操作的时候会自动去掉表别名 + * @return string|false + */ + protected function getDeleteTimeField(bool $read = false) + { + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; + + if (false === $field) { + return false; + } + + if (false === strpos($field, '.')) { + $field = '__TABLE__.' . $field; + } + + if (!$read && strpos($field, '.')) { + $array = explode('.', $field); + $field = array_pop($array); + } + + return $field; + } + + /** + * 查询的时候默认排除软删除数据 + * @access protected + * @param Query $query + * @return void + */ + protected function withNoTrashed(Query $query): void + { + $field = $this->getDeleteTimeField(true); + + if ($field) { + $condition = is_null($this->defaultSoftDelete) ? ['null', ''] : ['=', $this->defaultSoftDelete]; + $query->useSoftDelete($field, $condition); + } + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/TimeStamp.php b/vendor/topthink/think-orm/src/model/concern/TimeStamp.php new file mode 100644 index 0000000..e207961 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/TimeStamp.php @@ -0,0 +1,208 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use DateTime; + +/** + * 自动时间戳 + */ +trait TimeStamp +{ + /** + * 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 + * @var bool|string + */ + protected $autoWriteTimestamp; + + /** + * 创建时间字段 false表示关闭 + * @var false|string + */ + protected $createTime = 'create_time'; + + /** + * 更新时间字段 false表示关闭 + * @var false|string + */ + protected $updateTime = 'update_time'; + + /** + * 时间字段显示格式 + * @var string + */ + protected $dateFormat; + + /** + * 是否需要自动写入时间字段 + * @access public + * @param bool|string $auto + * @return $this + */ + public function isAutoWriteTimestamp($auto) + { + $this->autoWriteTimestamp = $this->checkTimeFieldType($auto); + + return $this; + } + + /** + * 检测时间字段的实际类型 + * @access public + * @param bool|string $type + * @return mixed + */ + protected function checkTimeFieldType($type) + { + if (true === $type) { + if (isset($this->type[$this->createTime])) { + $type = $this->type[$this->createTime]; + } elseif (isset($this->schema[$this->createTime]) && in_array($this->schema[$this->createTime], ['datetime', 'date', 'timestamp', 'int'])) { + $type = $this->schema[$this->createTime]; + } else { + $type = $this->getFieldType($this->createTime); + } + } + + return $type; + } + + /** + * 获取自动写入时间字段 + * @access public + * @return bool|string + */ + public function getAutoWriteTimestamp() + { + return $this->autoWriteTimestamp; + } + + /** + * 设置时间字段格式化 + * @access public + * @param string|false $format + * @return $this + */ + public function setDateFormat($format) + { + $this->dateFormat = $format; + + return $this; + } + + /** + * 获取自动写入时间字段 + * @access public + * @return string|false + */ + public function getDateFormat() + { + return $this->dateFormat; + } + + /** + * 自动写入时间戳 + * @access protected + * @return mixed + */ + protected function autoWriteTimestamp() + { + // 检测时间字段类型 + $type = $this->checkTimeFieldType($this->autoWriteTimestamp); + + return is_string($type) ? $this->getTimeTypeValue($type) : time(); + } + + /** + * 获取指定类型的时间字段值 + * @access protected + * @param string $type 时间字段类型 + * @return mixed + */ + protected function getTimeTypeValue(string $type) + { + $value = time(); + + switch ($type) { + case 'datetime': + case 'date': + case 'timestamp': + $value = $this->formatDateTime('Y-m-d H:i:s.u'); + break; + default: + if (false !== strpos($type, '\\')) { + // 对象数据写入 + $obj = new $type(); + if (method_exists($obj, '__toString')) { + // 对象数据写入 + $value = $obj->__toString(); + } + } + } + + return $value; + } + + /** + * 时间日期字段格式化处理 + * @access protected + * @param mixed $format 日期格式 + * @param mixed $time 时间日期表达式 + * @param bool $timestamp 时间表达式是否为时间戳 + * @return mixed + */ + protected function formatDateTime($format, $time = 'now', bool $timestamp = false) + { + if (empty($time)) { + return; + } + + if (false === $format) { + return $time; + } elseif (false !== strpos($format, '\\')) { + return new $format($time); + } + + if ($time instanceof DateTime) { + $dateTime = $time; + } elseif ($timestamp) { + $dateTime = new DateTime(); + $dateTime->setTimestamp((int) $time); + } else { + $dateTime = new DateTime($time); + } + + return $dateTime->format($format); + } + + /** + * 获取时间字段值 + * @access protected + * @param mixed $value + * @return mixed + */ + protected function getTimestampValue($value) + { + $type = $this->checkTimeFieldType($this->autoWriteTimestamp); + + if (is_string($type) && in_array(strtolower($type), [ + 'datetime', 'date', 'timestamp', + ])) { + $value = $this->formatDateTime($this->dateFormat, $value); + } else { + $value = $this->formatDateTime($this->dateFormat, $value, true); + } + + return $value; + } +} diff --git a/vendor/topthink/think-orm/src/model/relation/BelongsTo.php b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php new file mode 100644 index 0000000..76c7019 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php @@ -0,0 +1,331 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\helper\Str; +use think\Model; + +/** + * BelongsTo关联类 + */ +class BelongsTo extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, string $model, string $foreignKey, string $localKey, string $relation = null) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + $this->relation = $relation; + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $foreignKey = $this->foreignKey; + + $relationModel = $this->query + ->removeWhereField($this->localKey) + ->where($this->localKey, $this->parent->$foreignKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $this->parent); + } + + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 聚合字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', &$name = ''): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->whereExp($this->localKey, '=' . $this->parent->getTable() . '.' . $this->foreignKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $foreignKey = $this->foreignKey; + + if (!isset($result->$foreignKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where($this->localKey, '=', $result->$foreignKey) + ->$aggregate($field); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) { + $query->table([$table => $relation]) + ->field($relation . '.' . $localKey) + ->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } elseif ($where instanceof Query) { + $where->via($relation); + } elseif ($where instanceof Closure) { + $where($this->query->via($relation)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->field($fields) + ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $joinType ?: $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$foreignKey)) { + $range[] = $result->$foreignKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($localKey); + + $data = $this->eagerlyWhere([ + [$localKey, 'in', $range], + ], $localKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if ($relationModel && !empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($localKey); + + $data = $this->eagerlyWhere([ + [$localKey, '=', $result->$foreignKey], + ], $localKey, $subRelation, $closure, $cache); + + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if ($relationModel && !empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + + /** + * 添加关联数据 + * @access public + * @param Model $model关联模型对象 + * @return Model + */ + public function associate(Model $model): Model + { + $this->parent->setAttr($this->foreignKey, $model->getKey()); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate(): Model + { + $foreignKey = $this->foreignKey; + + $this->parent->setAttr($foreignKey, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->foreignKey})) { + // 关联查询带入关联条件 + $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php new file mode 100644 index 0000000..eccbfe9 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php @@ -0,0 +1,707 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\db\Raw; +use think\helper\Str; +use think\Model; +use think\model\Pivot; +use think\model\Relation; +use think\Paginator; + +/** + * 多对多关联类 + */ +class BelongsToMany extends Relation +{ + /** + * 中间表表名 + * @var string + */ + protected $middle; + + /** + * 中间表模型名称 + * @var string + */ + protected $pivotName; + + /** + * 中间表模型对象 + * @var Pivot + */ + protected $pivot; + + /** + * 中间表数据名称 + * @var string + */ + protected $pivotDataName = 'pivot'; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $middle 中间表/模型名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, string $model, string $middle, string $foreignKey, string $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + + if (false !== strpos($middle, '\\')) { + $this->pivotName = $middle; + $this->middle = class_basename($middle); + } else { + $this->middle = $middle; + } + + $this->query = (new $model)->db(); + $this->pivot = $this->newPivot(); + } + + /** + * 设置中间表模型 + * @access public + * @param $pivot + * @return $this + */ + public function pivot(string $pivot) + { + $this->pivotName = $pivot; + return $this; + } + + /** + * 设置中间表数据名称 + * @access public + * @param string $name + * @return $this + */ + public function name(string $name) + { + $this->pivotDataName = $name; + return $this; + } + + /** + * 实例化中间表模型 + * @access public + * @param $data + * @return Pivot + * @throws Exception + */ + protected function newPivot(array $data = []): Pivot + { + $class = $this->pivotName ?: Pivot::class; + $pivot = new $class($data, $this->parent, $this->middle); + + if ($pivot instanceof Pivot) { + return $pivot; + } else { + throw new Exception('pivot model must extends: \think\model\Pivot'); + } + } + + /** + * 合成中间表模型 + * @access protected + * @param array|Collection|Paginator $models + */ + protected function hydratePivot(iterable $models) + { + foreach ($models as $model) { + $pivot = []; + + foreach ($model->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($model->$key); + } + } + } + + $model->setRelation($this->pivotDataName, $this->newPivot($pivot)); + } + } + + /** + * 创建关联查询Query对象 + * @access protected + * @return Query + */ + protected function buildQuery(): Query + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + + // 关联查询 + $pk = $this->parent->getPk(); + + $condition = ['pivot.' . $localKey, '=', $this->parent->$pk]; + + return $this->belongsToManyQuery($foreignKey, $localKey, [$condition]); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $result = $this->buildQuery() + ->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载select方法 + * @access public + * @param mixed $data + * @return Collection + */ + public function select($data = null): Collection + { + $result = $this->buildQuery()->select($data); + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载paginate方法 + * @access public + * @param int|array $listRows + * @param int|bool $simple + * @return Paginator + */ + public function paginate($listRows = null, $simple = false): Paginator + { + $result = $this->buildQuery()->paginate($listRows, $simple); + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载find方法 + * @access public + * @param mixed $data + * @return Model + */ + public function find($data = null) + { + $result = $this->buildQuery()->find($data); + + if (!$result->isEmpty()) { + $this->hydratePivot([$result]); + } + + return $result; + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return Collection + */ + public function selectOrFail($data = null): Collection + { + return $this->buildQuery()->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return Model + */ + public function findOrFail($data = null): Model + { + return $this->buildQuery()->failException(true)->find($data); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Model + */ + public function has(string $operator = '>=', $count = 1, $id = '*', string $joinType = 'INNER', Query $query = null) + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + * @throws Exception + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 设置中间表的查询条件 + * @access public + * @param string $field + * @param string $op + * @param mixed $condition + * @return $this + */ + public function wherePivot($field, $op = null, $condition = null) + { + $this->query->where('pivot.' . $field, $op, $condition); + return $this; + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $pk = $resultSet[0]->getPk(); + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $localKey, 'in', $range], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, '=', $pk], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): float + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + $pk = $result->$pk; + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, '=', $pk], + ])->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + [ + 'pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . $this->parent->getPk()), + ], + ])->fetchSql()->$aggregate($field); + } + + /** + * 多对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyManyToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + + $key = $pivot[$this->localKey]; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $set->setRelation($this->pivotDataName, $this->newPivot($pivot)); + + $data[$key][] = $set; + } + + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access protected + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $table = $this->pivot->db()->getTable(); + $fields = $this->getQueryFields($tableName); + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + $query = $this->query + ->field($fields) + ->tableField(true, $table, 'pivot', 'pivot__'); + + if (empty($this->baseQuery)) { + $relationFk = $this->query->getPk(); + $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + + return $query; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param iterable $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return array|false + */ + public function saveAll(iterable $dataSet, array $pivot = [], bool $samePivot = false) + { + $result = []; + + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = $pivot[$key] ?? []; + } else { + $pivotData = $pivot; + } + + $result[] = $this->attach($data, $pivotData); + } + + return empty($result) ? false : $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception + */ + public function attach($data, array $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $id = $model->insertGetId($data); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + if (!empty($id)) { + // 保存中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + $ids = (array) $id; + + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $this->pivot->replace() + ->exists(false) + ->data([]) + ->save($pivot); + $result[] = $this->newPivot($pivot); + } + + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot|false + */ + public function attached($data) + { + if ($data instanceof Model) { + $id = $data->getKey(); + } else { + $id = $data; + } + + $pivot = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->foreignKey, $id) + ->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, bool $relationDel = false): int + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + // 删除中间表数据 + $pk = $this->parent->getPk(); + $pivot = []; + $pivot[] = [$this->localKey, '=', $this->parent->$pk]; + + if (isset($id)) { + $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id]; + } + + $result = $this->pivot->where($pivot)->delete(); + + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + + return $result; + } + + /** + * 数据同步 + * @access public + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync(array $ids, bool $detaching = true): array + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + + $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; + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasMany.php b/vendor/topthink/think-orm/src/model/relation/HasMany.php new file mode 100644 index 0000000..aa46a88 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasMany.php @@ -0,0 +1,367 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 一对多关联类 + */ +class HasMany extends Relation +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, string $model, string $foreignKey, string $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + return $this->query + ->where($this->foreignKey, $this->parent->{$this->localKey}) + ->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyOneToMany([ + [$this->foreignKey, 'in', $range], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $pk = $result->$localKey; + $data = $this->eagerlyOneToMany([ + [$this->foreignKey, '=', $pk], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query->alias($aggregate . '_table') + ->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyOneToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($this->foreignKey); + + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($where) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->with($subRelation) + ->select(); + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $key = $set->$foreignKey; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + $model = $this->make(); + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param iterable $dataSet 数据集 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + */ + public function saveAll(iterable $dataSet, bool $replace = true) + { + $result = []; + + foreach ($dataSet as $key => $data) { + $result[] = $this->save($data, $replace); + } + + return empty($result) ? false : $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER', Query $query = null): Query + { + $table = $this->query->getTable(); + + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if ('*' != $id) { + $id = $relation . '.' . (new $this->model)->getPk(); + } + + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->field($model . '.*') + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($relation . '.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } elseif ($where instanceof Query) { + $where->via($relation); + } elseif ($where instanceof Closure) { + $where($this->query->via($relation)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->group($model . '.' . $this->localKey) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php new file mode 100644 index 0000000..23367f3 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php @@ -0,0 +1,382 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 远程一对多关联类 + */ +class HasManyThrough extends Relation +{ + /** + * 中间关联表外键 + * @var string + */ + protected $throughKey; + + /** + * 中间主键 + * @var string + */ + protected $throughPk; + + /** + * 中间表查询对象 + * @var Query + */ + protected $through; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 关联模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 中间关联外键 + * @param string $localKey 当前模型主键 + * @param string $throughPk 中间模型主键 + */ + public function __construct(Model $parent, string $model, string $through, string $foreignKey, string $throughKey, string $localKey, string $throughPk) + { + $this->parent = $parent; + $this->model = $model; + $this->through = (new $through)->db(); + $this->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->throughPk = $throughPk; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + return $this->query->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + $model = Str::snake(class_basename($this->parent)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $relation = new $this->model; + $relationTable = $relation->getTable(); + $softDelete = $this->query->getOptions('soft_delete'); + + if ('*' != $id) { + $id = $relationTable . '.' . $relation->getPk(); + } + $query = $query ?: $this->parent->db()->alias($model); + + return $query->field($model . '.*') + ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($relationTable, $relationTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk) + ->when($softDelete, function ($query) use ($softDelete, $relationTable) { + $query->where($relationTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($relationTable . '.' . $this->throughKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, $joinType = '', Query $query = null): Query + { + $model = Str::snake(class_basename($this->parent)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = (new $this->model)->getTable(); + + if (is_array($where)) { + $this->getQueryWhere($where, $modelTable); + } elseif ($where instanceof Query) { + $where->via($modelTable); + } elseif ($where instanceof Closure) { + $where($this->query->via($modelTable)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($modelTable, $modelTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk) + ->when($softDelete, function ($query) use ($softDelete, $modelTable) { + $query->where($modelTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($modelTable . '.' . $this->throughKey) + ->where($where) + ->field($fields); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$this->foreignKey, 'in', $range], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + // 设置关联属性 + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $pk = $result->$localKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $pk], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + + /** + * 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + $throughList = $this->through->where($where)->select(); + $keys = $throughList->column($this->throughPk, $this->throughPk); + + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($this->throughKey, 'in', $keys) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + $keys = $throughList->column($this->foreignKey, $this->throughPk); + + foreach ($list as $set) { + $key = $keys[$set->{$this->throughKey}]; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return mixed + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + $alias = Str::snake(class_basename($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $result->$localKey) + ->$aggregate($field); + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + $alias = Str::snake(class_basename($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->whereExp($throughTable . '.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $alias = Str::snake(class_basename($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + $fields = $this->getQueryFields($alias); + + $this->query + ->field($fields) + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasOne.php b/vendor/topthink/think-orm/src/model/relation/HasOne.php new file mode 100644 index 0000000..98bdf89 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasOne.php @@ -0,0 +1,300 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\helper\Str; +use think\Model; + +/** + * HasOne 关联类 + */ +class HasOne extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, string $model, string $foreignKey, string $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + $localKey = $this->localKey; + + if ($closure) { + $closure($this->getClosureType($closure)); + } + + // 判断关联类型执行查询 + $relationModel = $this->query + ->removeWhereField($this->foreignKey) + ->where($this->foreignKey, $this->parent->$localKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $this->parent); + } + + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) { + $query->table([$table => $relation]) + ->field($relation . '.' . $foreignKey) + ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } elseif ($where instanceof Query) { + $where->via($relation); + } elseif ($where instanceof Closure) { + $where($this->query->via($relation)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType ?: $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, 'in', $range], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if ($relationModel && !empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if ($relationModel && !empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result); + } else { + $result->setRelation($relation, $relationModel); + } + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php b/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php new file mode 100644 index 0000000..8ec42df --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php @@ -0,0 +1,163 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\helper\Str; +use think\Model; + +/** + * 远程一对一关联类 + */ +class HasOneThrough extends HasManyThrough +{ + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + $relationModel = $this->query->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$this->foreignKey, 'in', $range], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + $result->setRelation($relation, $relationModel); + } + + /** + * 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + $keys = $this->through->where($where)->column($this->throughPk, $this->foreignKey); + + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($this->throughKey, 'in', $keys) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + $keys = array_flip($keys); + + foreach ($list as $set) { + $data[$keys[$set->{$this->throughKey}]] = $set; + } + + return $data; + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphMany.php b/vendor/topthink/think-orm/src/model/relation/MorphMany.php new file mode 100644 index 0000000..82910cb --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphMany.php @@ -0,0 +1,353 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 多态一对多关联 + */ +class MorphMany extends Relation +{ + + /** + * 多态关联外键 + * @var string + */ + protected $morphKey; + /** + * 多态字段名 + * @var string + */ + protected $morphType; + + /** + * 多态类型 + * @var string + */ + protected $type; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + return $this->query->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $where = [ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ]; + $data = $this->eagerlyMorphToMany($where, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $key = $result->$pk; + $data = $this->eagerlyMorphToMany([ + [$this->morphKey, '=', $key], + [$this->morphType, '=', $this->type], + ], $subRelation, $closure, $cache); + + if (!isset($data[$key])) { + $data[$key] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$key], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return mixed + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where([ + [$this->morphKey, '=', $result->$pk], + [$this->morphType, '=', $this->type], + ]) + ->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->whereExp($this->morphKey, '=' . $this->parent->getTable() . '.' . $this->parent->getPk()) + ->where($this->morphType, '=', $this->type) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 多态一对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyMorphToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + $this->query->removeOption('where'); + + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + $morphKey = $this->morphKey; + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $key = $set->$morphKey; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param bool $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + $model = $this->make(); + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param iterable $dataSet 数据集 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + */ + public function saveAll(iterable $dataSet, bool $replace = true) + { + $result = []; + + foreach ($dataSet as $key => $data) { + $result[] = $this->save($data, $replace); + } + + return empty($result) ? false : $result; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphOne.php b/vendor/topthink/think-orm/src/model/relation/MorphOne.php new file mode 100644 index 0000000..6789c76 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphOne.php @@ -0,0 +1,280 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 多态一对一关联类 + */ +class MorphOne extends Relation +{ + /** + * 多态关联外键 + * @var string + */ + protected $morphKey; + + /** + * 多态字段 + * @var string + */ + protected $morphType; + + /** + * 多态类型 + * @var string + */ + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + $relationModel = $this->query->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null) + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToOne([ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$pk]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + $result->setRelation($relation, $relationModel); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + $data = $this->eagerlyMorphToOne([ + [$this->morphKey, '=', $pk], + [$this->morphType, '=', $this->type], + ], $subRelation, $closure, $cache); + + if (isset($data[$pk])) { + $relationModel = $data[$pk]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } else { + $relationModel = null; + } + + $result->setRelation($relation, $relationModel); + } + } + + /** + * 多态一对一 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyMorphToOne(array $where, array $subRelation = [], $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + $morphKey = $this->morphKey; + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $data[$set->$morphKey] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + $model = $this->make(); + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphTo.php b/vendor/topthink/think-orm/src/model/relation/MorphTo.php new file mode 100644 index 0000000..c939c1d --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphTo.php @@ -0,0 +1,333 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 多态关联类 + */ +class MorphTo extends Relation +{ + /** + * 多态关联外键 + * @var string + */ + protected $morphKey; + + /** + * 多态字段 + * @var string + */ + protected $morphType; + + /** + * 多态别名 + * @var array + */ + protected $alias = []; + + /** + * 关联名 + * @var string + */ + protected $relation; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, string $morphType, string $morphKey, array $alias = [], string $relation = null) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + $this->relation = $relation; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel(): Model + { + $morphType = $this->morphType; + $model = $this->parseModel($this->parent->$morphType); + + return (new $model); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + // 多态模型 + $model = $this->parseModel($this->parent->$morphType); + + // 主键数据 + $pk = $this->parent->$morphKey; + + $relationModel = (new $model)->relation($subRelation)->find($pk); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null) + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel(string $model): string + { + if (isset($this->alias[$model])) { + $model = $this->alias[$model]; + } + + if (false === strpos($model, '\\')) { + $path = explode('\\', get_class($this->parent)); + array_pop($path); + array_push($path, Str::studly($model)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias(array $alias) + { + $this->alias = $alias; + + return $this; + } + + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption() + { + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + $pk = $obj->getPk(); + $list = $obj->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select($val); + $data = []; + + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + $relationModel = null; + throw new Exception('relation data not exists :' . $this->model); + } else { + $relationModel = $data[$result->$morphKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + $result->setRelation($relation, $relationModel); + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation, $cache); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*') + {} + + /** + * 多态MorphTo 关联模型预查询 + * @access protected + * @param string $model 关联模型对象 + * @param string $relation 关联名 + * @param Model $result + * @param array $subRelation 子关联 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlyMorphToOne(string $model, string $relation, Model $result, array $subRelation = [], array $cache = []): void + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->find($pk); + + if ($data) { + $data->setParent(clone $result); + $data->exists(true); + } + + $result->setRelation($relation, $data ?: null); + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @param string $type 多态类型 + * @return Model + */ + public function associate(Model $model, string $type = ''): Model + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $pk = $model->getPk(); + + $this->parent->setAttr($morphKey, $model->$pk); + $this->parent->setAttr($morphType, $type ?: get_class($model)); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate(): Model + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + $this->parent->setAttr($morphKey, null); + $this->parent->setAttr($morphType, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/OneToOne.php b/vendor/topthink/think-orm/src/model/relation/OneToOne.php new file mode 100644 index 0000000..540fcc2 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/OneToOne.php @@ -0,0 +1,332 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 一对一关联基础类 + * @package think\model\relation + */ +abstract class OneToOne extends Relation +{ + /** + * JOIN类型 + * @var string + */ + protected $joinType = 'INNER'; + + /** + * 绑定的关联属性 + * @var array + */ + protected $bindAttr = []; + + /** + * 关联名 + * @var string + */ + protected $relation; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType(string $type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param mixed $field 关联字段 + * @param string $joinType JOIN方式 + * @param Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, string $relation, $field = true, string $joinType = '', Closure $closure = null, bool $first = false): void + { + $name = Str::snake(class_basename($this->parent)); + + if ($first) { + $table = $query->getTable(); + $query->table([$table => $name]); + + if ($query->getOptions('field')) { + $masterField = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $masterField = true; + } + + $query->tableField($masterField, $table, $name); + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $joinType = $joinType ?: $this->joinType; + + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + $joinOn = $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey; + } else { + $joinOn = $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey; + } + + if ($closure) { + // 执行闭包查询 + $closure($this->getClosureType($closure)); + + // 使用withField指定获取关联的字段 + if ($this->withField) { + $field = $this->withField; + } + } + + $query->join([$joinTable => $joinAlias], $joinOn, $joinType) + ->tableField($field, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet + * @param string $relation + * @param array $subRelation + * @param Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null); + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result + * @param string $relation + * @param array $subRelation + * @param Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @param bool $join 是否为JOIN方式 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = [], bool $join = false): void + { + if ($join) { + // 模型JOIN关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } else { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure, $cache); + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @param bool $join 是否为JOIN方式 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = [], bool $join = false): void + { + if ($join) { + // 模型JOIN关联组装 + $this->match($this->model, $relation, $result); + } else { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure, $cache); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + $model = new $this->model; + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param array $attr 要绑定的属性列表 + * @return $this + */ + public function bind(array $attr) + { + $this->bindAttr = $attr; + + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr(): array + { + return $this->bindAttr; + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match(string $model, string $relation, Model $result): void + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + if (isset($list[$relation])) { + $array = array_unique($list[$relation]); + + if (count($array) == 1 && null === current($array)) { + $relationModel = null; + } else { + $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if ($relationModel && !empty($this->bindAttr)) { + $this->bindAttr($relationModel, $result); + } + } else { + $relationModel = null; + } + + $result->setRelation($relation, $relationModel); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $model 关联模型对象 + * @param Model $result 父模型对象 + * @return void + * @throws Exception + */ + protected function bindAttr(Model $model, Model $result): void + { + foreach ($this->bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $result->getOrigin($key); + + if (!is_null($value)) { + throw new Exception('bind attr has exists:' . $key); + } + + $result->setAttr($key, $model ? $model->$attr : null); + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + if ($this->withField) { + $this->query->field($this->withField); + } + + if ($this->query->getOptions('order')) { + $this->query->group($key); + } + + $list = $this->query + ->where($where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + if (!isset($data[$set->$key])) { + $data[$set->$key] = $set; + } + } + + return $data; + } + +} diff --git a/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php b/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php new file mode 100644 index 0000000..6d55c39 --- /dev/null +++ b/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- + +namespace think\paginator\driver; + +use think\Paginator; + +/** + * Bootstrap 分页驱动 + */ +class Bootstrap extends Paginator +{ + + /** + * 上一页按钮 + * @param string $text + * @return string + */ + protected function getPreviousButton(string $text = "«"): string + { + + if ($this->currentPage() <= 1) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url( + $this->currentPage() - 1 + ); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 下一页按钮 + * @param string $text + * @return string + */ + protected function getNextButton(string $text = '»'): string + { + if (!$this->hasMore) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url($this->currentPage() + 1); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 页码按钮 + * @return string + */ + protected function getLinks(): string + { + if ($this->simple) { + return ''; + } + + $block = [ + 'first' => null, + 'slider' => null, + 'last' => null, + ]; + + $side = 3; + $window = $side * 2; + + if ($this->lastPage < $window + 6) { + $block['first'] = $this->getUrlRange(1, $this->lastPage); + } elseif ($this->currentPage <= $window) { + $block['first'] = $this->getUrlRange(1, $window + 2); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } elseif ($this->currentPage > ($this->lastPage - $window)) { + $block['first'] = $this->getUrlRange(1, 2); + $block['last'] = $this->getUrlRange($this->lastPage - ($window + 2), $this->lastPage); + } else { + $block['first'] = $this->getUrlRange(1, 2); + $block['slider'] = $this->getUrlRange($this->currentPage - $side, $this->currentPage + $side); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } + + $html = ''; + + if (is_array($block['first'])) { + $html .= $this->getUrlLinks($block['first']); + } + + if (is_array($block['slider'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['slider']); + } + + if (is_array($block['last'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['last']); + } + + return $html; + } + + /** + * 渲染分页html + * @return mixed + */ + public function render() + { + if ($this->hasPages()) { + if ($this->simple) { + return sprintf( + '
    %s %s
', + $this->getPreviousButton(), + $this->getNextButton() + ); + } else { + return sprintf( + '
    %s %s %s
', + $this->getPreviousButton(), + $this->getLinks(), + $this->getNextButton() + ); + } + } + } + + /** + * 生成一个可点击的按钮 + * + * @param string $url + * @param string $page + * @return string + */ + protected function getAvailablePageWrapper(string $url, string $page): string + { + return '
  • ' . $page . '
  • '; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper(string $text): string + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper(string $text): string + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成省略号按钮 + * + * @return string + */ + protected function getDots(): string + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * 批量生成页码按钮. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls): string + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * 生成普通页码按钮 + * + * @param string $url + * @param string $page + * @return string + */ + protected function getPageLinkWrapper(string $url, string $page): string + { + if ($this->currentPage() == $page) { + return $this->getActivePageWrapper($page); + } + + return $this->getAvailablePageWrapper($url, $page); + } +} diff --git a/vendor/topthink/think-socketlog/LICENSE b/vendor/topthink/think-socketlog/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-socketlog/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-socketlog/README.md b/vendor/topthink/think-socketlog/README.md new file mode 100644 index 0000000..6d8d2b6 --- /dev/null +++ b/vendor/topthink/think-socketlog/README.md @@ -0,0 +1,11 @@ +SocketLog for ThinkPHP 6.0 +=============== + + +然后,配置应用的日志配置文件`log.php`的`type`参数为: + +~~~ +'type' => 'SocketLog', +~~~ + + diff --git a/vendor/topthink/think-socketlog/composer.json b/vendor/topthink/think-socketlog/composer.json new file mode 100644 index 0000000..1d806cd --- /dev/null +++ b/vendor/topthink/think-socketlog/composer.json @@ -0,0 +1,22 @@ +{ + "name": "topthink/think-socketlog", + "description": "SocketLog for thinkphp", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "topthink/framework": "^6.0.0" + }, + "autoload": { + "psr-4": { + "think\\log\\driver\\": "src" + }, + "files": [ + ] + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/topthink/think-socketlog/src/SocketLog.php b/vendor/topthink/think-socketlog/src/SocketLog.php new file mode 100644 index 0000000..13bae4f --- /dev/null +++ b/vendor/topthink/think-socketlog/src/SocketLog.php @@ -0,0 +1,280 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; +use think\contract\LogHandlerInterface; + +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +class SocketLog implements LogHandlerInterface +{ + protected $app; + + 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 + + /** + * 架构函数 + * @access public + * @param App $app 应用对象 + * @param array $config 缓存参数 + */ + public function __construct(App $app, array $config = []) + { + $this->app = $app; + + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log): bool + { + if (!$this->check()) { + return false; + } + + $trace = []; + + if ($this->app->isDebug()) { + $runtime = round(microtime(true) - $this->app->getBeginTime(), 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() - $this->app->getBeginMem()) / 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' => $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; + } + + /** + * 发送给指定客户端 + * @access protected + * @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; + } + + /** + * @access protected + * @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/vendor/topthink/think-swoole/.gitignore b/vendor/topthink/think-swoole/.gitignore new file mode 100644 index 0000000..ccdd1dc --- /dev/null +++ b/vendor/topthink/think-swoole/.gitignore @@ -0,0 +1,6 @@ + +.DS_Store +*.xml +.idea/think-swoole.iml +composer.lock +vendor \ No newline at end of file diff --git a/vendor/topthink/think-swoole/LICENSE b/vendor/topthink/think-swoole/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-swoole/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-swoole/README.md b/vendor/topthink/think-swoole/README.md new file mode 100644 index 0000000..c8789b1 --- /dev/null +++ b/vendor/topthink/think-swoole/README.md @@ -0,0 +1,36 @@ +ThinkPHP Swoole 扩展 +=============== + +## 安装 + +首先按照Swoole官网说明安装swoole扩展,然后使用 +~~~ +composer require topthink/think-swoole +~~~ +安装swoole扩展。 + +## 使用方法 + + +直接在命令行下启动HTTP服务端。 + +~~~ +php think swoole +~~~ + +启动完成后,默认会在0.0.0.0:80启动一个HTTP Server,可以直接访问当前的应用。 + +swoole的相关参数可以在`config/swoole.php`里面配置(具体参考配置文件内容)。 + +如果需要使用守护进程方式运行,可以配置 + +~~~ +'options' => [ + 'daemonize' => true +] +~~~ + +支持的操作包括 +~~~ +php think swoole [start|stop|reload|restart] +~~~ \ No newline at end of file diff --git a/vendor/topthink/think-swoole/composer.json b/vendor/topthink/think-swoole/composer.json new file mode 100644 index 0000000..6dff53f --- /dev/null +++ b/vendor/topthink/think-swoole/composer.json @@ -0,0 +1,42 @@ +{ + "name": "topthink/think-swoole", + "description": "Swoole extend for thinkphp", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "ext-json": "*", + "ext-swoole": ">=4.0.0", + "topthink/framework": "^6.0", + "topthink/think-helper": "^3.0.4", + "symfony/finder": "^4.3.2", + "swoole/ide-helper": "^4.3", + "nette/php-generator": "^3.2" + }, + "autoload": { + "psr-4": { + "think\\swoole\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "extra": { + "think": { + "services": [ + "think\\swoole\\Service" + ], + "config": { + "swoole": "src/config/swoole.php" + } + } + }, + "minimum-stability": "dev", + "require-dev": { + "symfony/var-dumper": "^4.3" + } +} diff --git a/vendor/topthink/think-swoole/src/App.php b/vendor/topthink/think-swoole/src/App.php new file mode 100644 index 0000000..30846f7 --- /dev/null +++ b/vendor/topthink/think-swoole/src/App.php @@ -0,0 +1,32 @@ +isRpcInterface($abstract) && !$this->bound($abstract)) { + //rpc接口 + $this->bind($abstract, Proxy::getClassName($abstract)); + } + + return parent::make($abstract, $vars, $newInstance); + } +} diff --git a/vendor/topthink/think-swoole/src/FileWatcher.php b/vendor/topthink/think-swoole/src/FileWatcher.php new file mode 100644 index 0000000..bef4cb2 --- /dev/null +++ b/vendor/topthink/think-swoole/src/FileWatcher.php @@ -0,0 +1,51 @@ +finder = new Finder(); + $this->finder->files() + ->name($name) + ->in($directory) + ->exclude($exclude); + } + + protected function findFiles() + { + $files = []; + /** @var SplFileInfo $f */ + foreach ($this->finder as $f) { + $files[$f->getRealpath()] = $f->getMTime(); + } + return $files; + } + + public function watch(callable $callback) + { + $this->files = $this->findFiles(); + + swoole_timer_tick(1000, function () use ($callback) { + + $files = $this->findFiles(); + + foreach ($files as $path => $time) { + if (empty($this->files[$path]) || $this->files[$path] != $time) { + call_user_func($callback); + break; + } + } + + $this->files = $files; + }); + } +} diff --git a/vendor/topthink/think-swoole/src/Http.php b/vendor/topthink/think-swoole/src/Http.php new file mode 100644 index 0000000..22f8d38 --- /dev/null +++ b/vendor/topthink/think-swoole/src/Http.php @@ -0,0 +1,66 @@ +app->middleware; + } + + $middleware = clone self::$middleware; + + $app = $this->app; + $closure = function () use ($app) { + $this->app = $app; + }; + + $resetMiddleware = $closure->bindTo($middleware, $middleware); + $resetMiddleware(); + + $this->app->instance("middleware", $middleware); + } + + protected function loadRoutes(): void + { + if (!isset(self::$route)) { + parent::loadRoutes(); + self::$route = clone $this->app->route; + } + } + + protected function dispatchToRoute($request) + { + if (isset(self::$route)) { + $newRoute = clone self::$route; + $app = $this->app; + $closure = function () use ($app) { + $this->app = $app; + }; + + $resetRouter = $closure->bindTo($newRoute, $newRoute); + $resetRouter(); + + $this->app->instance("route", $newRoute); + } + + return parent::dispatchToRoute($request); + } +} diff --git a/vendor/topthink/think-swoole/src/Manager.php b/vendor/topthink/think-swoole/src/Manager.php new file mode 100644 index 0000000..d5b9dfc --- /dev/null +++ b/vendor/topthink/think-swoole/src/Manager.php @@ -0,0 +1,282 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole; + +use Closure; +use Exception; +use Swoole\Process; +use Swoole\Server; +use think\App; +use think\console\Output; +use think\exception\Handle; +use think\helper\Str; +use think\swoole\App as SwooleApp; +use think\swoole\concerns\InteractsWithHttp; +use think\swoole\concerns\InteractsWithRpc; +use think\swoole\concerns\InteractsWithServer; +use think\swoole\concerns\InteractsWithSwooleTable; +use think\swoole\concerns\InteractsWithWebsocket; +use think\swoole\pool\Cache; +use think\swoole\pool\Db; +use Throwable; + +/** + * Class Manager + */ +class Manager +{ + use InteractsWithServer, InteractsWithSwooleTable, InteractsWithHttp, InteractsWithWebsocket, InteractsWithRpc; + + /** + * @var App + */ + protected $container; + + /** + * @var SwooleApp + */ + protected $app; + + /** @var PidManager */ + protected $pidManager; + + /** + * Server events. + * + * @var array + */ + protected $events = [ + 'start', + 'shutDown', + 'workerStart', + 'workerStop', + 'workerError', + 'packet', + 'task', + 'finish', + 'pipeMessage', + 'managerStart', + 'managerStop', + 'request', + ]; + + /** + * Manager constructor. + * @param App $container + * @param PidManager $pidManager + */ + public function __construct(App $container, PidManager $pidManager) + { + $this->container = $container; + $this->pidManager = $pidManager; + } + + /** + * 启动服务 + */ + public function run(): void + { + $this->initialize(); + if ($this->getConfig('hot_update.enable', false)) { + //热更新 + $this->addHotUpdateProcess(); + } + + $this->getServer()->start(); + } + + /** + * 停止服务 + */ + public function stop(): void + { + $this->getServer()->shutdown(); + } + + /** + * Initialize. + */ + protected function initialize(): void + { + $this->createTables(); + $this->prepareWebsocket(); + $this->setSwooleServerListeners(); + $this->prepareRpc(); + } + + /** + * 获取配置 + * @param string $name + * @param null $default + * @return mixed + */ + public function getConfig(string $name, $default = null) + { + return $this->container->config->get("swoole.{$name}", $default); + } + + /** + * 触发事件 + * @param $event + * @param $params + */ + protected function triggerEvent(string $event, $params = null): void + { + $this->container->event->trigger("swoole.{$event}", $params); + } + + /** + * 监听事件 + * @param string $event + * @param $listener + * @param bool $first + */ + protected function onEvent(string $event, $listener, bool $first = false): void + { + $this->container->event->listen("swoole.{$event}", $listener, $first); + } + + /** + * @return Server + */ + public function getServer() + { + return $this->container->make(Server::class); + } + + /** + * Set swoole server listeners. + */ + protected function setSwooleServerListeners() + { + foreach ($this->events as $event) { + $listener = Str::camel("on_$event"); + $callback = method_exists($this, $listener) ? [$this, $listener] : function () use ($event) { + $this->triggerEvent($event, func_get_args()); + }; + + $this->getServer()->on($event, $callback); + } + } + + protected function prepareApplication() + { + if (!$this->app instanceof SwooleApp) { + $this->app = new SwooleApp($this->container->getRootPath()); + $this->app->bind(SwooleApp::class, App::class); + //绑定连接池 + if ($this->getConfig('pool.db.enable', true)) { + $this->app->bind('db', Db::class); + } + if ($this->getConfig('pool.cache.enable', true)) { + $this->app->bind('cache', Cache::class); + } + $this->app->initialize(); + $this->prepareConcretes(); + } + } + + /** + * 预加载 + */ + protected function prepareConcretes() + { + $defaultConcretes = ['db', 'cache', 'event']; + + $concretes = array_merge($defaultConcretes, $this->getConfig('concretes', [])); + + foreach ($concretes as $concrete) { + if ($this->app->has($concrete)) { + $this->app->make($concrete); + } + } + } + + /** + * 清除apc、op缓存 + */ + protected function clearCache() + { + if (extension_loaded('apc')) { + apc_clear_cache(); + } + + if (extension_loaded('Zend OPcache')) { + opcache_reset(); + } + } + + /** + * Set process name. + * + * @param $process + */ + protected function setProcessName($process) + { + // Mac OSX不支持进程重命名 + if (stristr(PHP_OS, 'DAR')) { + return; + } + + $serverName = 'swoole_http_server'; + $appName = $this->container->config->get('app.name', 'ThinkPHP'); + + $name = sprintf('%s: %s for %s', $serverName, $process, $appName); + + swoole_set_process_name($name); + } + + /** + * 热更新 + */ + protected function addHotUpdateProcess() + { + $process = new Process(function () { + $watcher = new FileWatcher($this->getConfig('hot_update.include', []), $this->getConfig('hot_update.exclude', []), $this->getConfig('hot_update.name', [])); + + $watcher->watch(function () { + $this->getServer()->reload(); + }); + }, false, 0); + + $this->getServer()->addProcess($process); + } + + /** + * 在沙箱中执行 + * @param Closure $callable + * @param null $fd + * @param bool $persistent + */ + protected function runInSandbox(Closure $callable, $fd = null, $persistent = false) + { + /** @var Sandbox $sandbox */ + $sandbox = $this->app->make(Sandbox::class); + + $sandbox->run($callable, $fd, $persistent); + } + + /** + * Log server error. + * + * @param Throwable|Exception $e + */ + public function logServerError(Throwable $e) + { + /** @var Handle $handle */ + $handle = $this->container->make(Handle::class); + + $handle->renderForConsole(new Output(), $e); + + $handle->report($e); + } +} diff --git a/vendor/topthink/think-swoole/src/PidManager.php b/vendor/topthink/think-swoole/src/PidManager.php new file mode 100644 index 0000000..f6e3548 --- /dev/null +++ b/vendor/topthink/think-swoole/src/PidManager.php @@ -0,0 +1,119 @@ +file = $file ?? (sys_get_temp_dir() . '/swoole.pid'); + } + + public function create(int $masterPid, int $managerPid) + { + if (!is_writable($this->file) + && !is_writable(dirname($this->file)) + ) { + throw new RuntimeException( + sprintf('Pid file "%s" is not writable', $this->file) + ); + } + + file_put_contents($this->file, $masterPid . ',' . $managerPid); + } + + public function getMasterPid() + { + return $this->getPids()['masterPid']; + } + + public function getManagerPid() + { + return $this->getPids()['managerPid']; + } + + public function getPids(): array + { + $pids = []; + + if (is_readable($this->file)) { + $content = file_get_contents($this->file); + $pids = explode(',', $content); + } + + return [ + 'masterPid' => $pids[0] ?? null, + 'managerPid' => $pids[1] ?? null, + ]; + } + + /** + * 是否运行中 + * @return bool + */ + public function isRunning() + { + $pids = $this->getPids(); + + if (!count($pids)) { + return false; + } + + $masterPid = $pids['masterPid'] ?? null; + $managerPid = $pids['managerPid'] ?? null; + + if ($managerPid) { + // Swoole process mode + return $masterPid && $managerPid && Process::kill((int) $managerPid, 0); + } + + // Swoole base mode, no manager process + return $masterPid && Process::kill((int) $masterPid, 0); + } + + /** + * Kill process. + * + * @param int $sig + * @param int $wait + * + * @return bool + */ + public function killProcess($sig, $wait = 0) + { + Process::kill( + Arr::first($this->getPids()), + $sig + ); + + if ($wait) { + $start = time(); + + do { + if (!$this->isRunning()) { + break; + } + + usleep(100000); + } while (time() < $start + $wait); + } + + return $this->isRunning(); + } + + public function remove(): bool + { + if (is_writable($this->file)) { + return unlink($this->file); + } + + return false; + } +} diff --git a/vendor/topthink/think-swoole/src/Sandbox.php b/vendor/topthink/think-swoole/src/Sandbox.php new file mode 100644 index 0000000..87f6890 --- /dev/null +++ b/vendor/topthink/think-swoole/src/Sandbox.php @@ -0,0 +1,255 @@ +manager = $manager; + $this->setBaseApp($app); + $this->initialize(); + } + + public function setBaseApp(Container $app) + { + $this->app = $app; + + return $this; + } + + public function getBaseApp() + { + return $this->app; + } + + protected function initialize() + { + Container::setInstance(function () { + return $this->getApplication(); + }); + + $this->app->bind(Http::class, \think\swoole\Http::class); + + $this->setInitialConfig(); + $this->setInitialServices(); + $this->setInitialEvent(); + $this->setInitialResetters(); + //兼容var-dumper + $this->compatibleVarDumper(); + + return $this; + } + + public function run(Closure $callable, $fd = null, $persistent = false) + { + $this->init($fd); + + try { + $this->getApplication()->invoke($callable, [$this]); + } catch (Throwable $e) { + $this->manager->logServerError($e); + } finally { + $this->clear(!$persistent); + } + } + + public function init($fd = null) + { + if (!is_null($fd)) { + Context::setData('_fd', $fd); + } + $this->setInstance($app = $this->getApplication()); + $this->resetApp($app); + } + + public function clear($snapshot = true) + { + if ($snapshot) { + unset($this->snapshots[$this->getSnapshotId()]); + } + + Context::clear(); + $this->setInstance($this->getBaseApp()); + gc_collect_cycles(); + } + + public function getApplication() + { + $snapshot = $this->getSnapshot(); + if ($snapshot instanceof Container) { + return $snapshot; + } + + $snapshot = clone $this->getBaseApp(); + $this->setSnapshot($snapshot); + + return $snapshot; + } + + protected function getSnapshotId() + { + if ($fd = Context::getData('_fd')) { + return "fd_" . $fd; + } else { + return Context::getCoroutineId(); + } + } + + /** + * Get current snapshot. + * @return App|null + */ + public function getSnapshot() + { + return $this->snapshots[$this->getSnapshotId()] ?? null; + } + + public function setSnapshot(Container $snapshot) + { + $this->snapshots[$this->getSnapshotId()] = $snapshot; + + return $this; + } + + public function setInstance(Container $app) + { + $app->instance('app', $app); + $app->instance(Container::class, $app); + } + + /** + * Set initial config. + */ + protected function setInitialConfig() + { + $this->config = clone $this->getBaseApp()->config; + } + + protected function setInitialEvent() + { + $this->event = clone $this->getBaseApp()->event; + } + + /** + * Get config snapshot. + */ + public function getConfig() + { + return $this->config; + } + + public function getEvent() + { + return $this->event; + } + + public function getServices() + { + return $this->services; + } + + protected function setInitialServices() + { + $app = $this->getBaseApp(); + + $services = [ + PaginatorService::class, + ]; + + $services = array_merge($services, $this->config->get('swoole.services', [])); + + foreach ($services as $service) { + if (class_exists($service) && !in_array($service, $this->services)) { + $serviceObj = new $service($app); + $this->services[$service] = $serviceObj; + } + } + } + + /** + * Initialize resetters. + */ + protected function setInitialResetters() + { + $app = $this->getBaseApp(); + + $resetters = [ + ClearInstances::class, + ResetConfig::class, + ResetEvent::class, + ResetService::class, + ]; + + $resetters = array_merge($resetters, $this->config->get('swoole.resetters', [])); + + foreach ($resetters as $resetter) { + $resetterClass = $app->make($resetter); + if (!$resetterClass instanceof ResetterInterface) { + throw new RuntimeException("{$resetter} must implement " . ResetterInterface::class); + } + $this->resetters[$resetter] = $resetterClass; + } + } + + /** + * Reset Application. + * + * @param Container $app + */ + protected function resetApp(Container $app) + { + foreach ($this->resetters as $resetter) { + $resetter->handle($app, $this); + } + } + + protected function compatibleVarDumper() + { + if (class_exists(VarDumper::class)) { + $this->app->middleware->add(ResetVarDumper::class); + } + } + +} diff --git a/vendor/topthink/think-swoole/src/Service.php b/vendor/topthink/think-swoole/src/Service.php new file mode 100644 index 0000000..8123b3a --- /dev/null +++ b/vendor/topthink/think-swoole/src/Service.php @@ -0,0 +1,87 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole; + +use Swoole\Http\Server as HttpServer; +use Swoole\Server; +use Swoole\Websocket\Server as WebsocketServer; +use think\Route; +use think\swoole\command\RpcInterface; +use think\swoole\command\Server as ServerCommand; +use think\swoole\websocket\socketio\Controller; + +class Service extends \think\Service +{ + protected $isWebsocket = false; + + /** + * @var HttpServer | WebsocketServer + */ + protected static $server; + + public function register() + { + $this->isWebsocket = $this->app->config->get('swoole.websocket.enable', false); + + $this->app->bind(Server::class, function () { + if (is_null(static::$server)) { + $this->createSwooleServer(); + } + + return static::$server; + }); + + $this->app->bind("swoole.server", Server::class); + + $this->app->bind(PidManager::class, function () { + return new PidManager($this->app->config->get("swoole.server.options.pid_file")); + }); + } + + public function boot() + { + $this->commands(ServerCommand::class, RpcInterface::class); + + if ($this->isWebsocket) { + $this->registerRoutes(function (Route $route) { + $route->group(function () use ($route) { + $route->get('socket.io/', '@upgrade'); + $route->post('socket.io/', '@reject'); + }) + ->prefix(Controller::class) + ->allowCrossDomain([ + 'Access-Control-Allow-Credentials' => 'true', + 'X-XSS-Protection' => 0, + ]); + }); + } + } + + /** + * Create swoole server. + */ + protected function createSwooleServer() + { + $server = $this->isWebsocket ? WebsocketServer::class : HttpServer::class; + $config = $this->app->config; + $host = $config->get('swoole.server.host'); + $port = $config->get('swoole.server.port'); + $socketType = $config->get('swoole.server.socket_type', SWOOLE_SOCK_TCP); + $mode = $config->get('swoole.server.mode', SWOOLE_PROCESS); + + static::$server = new $server($host, $port, $mode, $socketType); + + $options = $config->get('swoole.server.options'); + + static::$server->set($options); + } +} diff --git a/vendor/topthink/think-swoole/src/Table.php b/vendor/topthink/think-swoole/src/Table.php new file mode 100644 index 0000000..b2327c7 --- /dev/null +++ b/vendor/topthink/think-swoole/src/Table.php @@ -0,0 +1,73 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole; + +use Swoole\Table as SwooleTable; + +class Table +{ + /** + * Registered swoole tables. + * + * @var array + */ + protected $tables = []; + + /** + * Add a swoole table to existing tables. + * + * @param string $name + * @param SwooleTable $table + * + * @return Table + */ + public function add(string $name, SwooleTable $table) + { + $this->tables[$name] = $table; + + return $this; + } + + /** + * Get a swoole table by its name from existing tables. + * + * @param string $name + * + * @return SwooleTable $table + */ + public function get(string $name) + { + return $this->tables[$name] ?? null; + } + + /** + * Get all existing swoole tables. + * + * @return array + */ + public function getAll() + { + return $this->tables; + } + + /** + * Dynamically access table. + * + * @param string $key + * + * @return SwooleTable + */ + public function __get($key) + { + return $this->get($key); + } +} diff --git a/vendor/topthink/think-swoole/src/Websocket.php b/vendor/topthink/think-swoole/src/Websocket.php new file mode 100644 index 0000000..0a2ce8e --- /dev/null +++ b/vendor/topthink/think-swoole/src/Websocket.php @@ -0,0 +1,228 @@ +server = $server; + $this->room = $room; + $this->parser = $parser; + } + + /** + * Set broadcast to true. + */ + public function broadcast(): self + { + Context::setData('websocket._broadcast', true); + + return $this; + } + + /** + * Get broadcast status value. + */ + public function isBroadcast() + { + return Context::getData('websocket._broadcast', false); + } + + /** + * Set multiple recipients fd or room names. + * + * @param integer, string, array + * + * @return $this + */ + public function to($values): self + { + $values = is_string($values) || is_integer($values) ? func_get_args() : $values; + + $to = Context::getData("websocket._to", []); + + foreach ($values as $value) { + if (!in_array($value, $to)) { + $to[] = $value; + } + } + + Context::setData("websocket._to", $to); + + return $this; + } + + /** + * Get push destinations (fd or room name). + */ + public function getTo() + { + return Context::getData("websocket._to", []); + } + + /** + * Join sender to multiple rooms. + * + * @param string, array $rooms + * + * @return $this + */ + public function join($rooms): self + { + $rooms = is_string($rooms) || is_integer($rooms) ? func_get_args() : $rooms; + + $this->room->add($this->getSender(), $rooms); + + return $this; + } + + /** + * Make sender leave multiple rooms. + * + * @param array $rooms + * + * @return $this + */ + public function leave($rooms = []): self + { + $rooms = is_string($rooms) || is_integer($rooms) ? func_get_args() : $rooms; + + $this->room->delete($this->getSender(), $rooms); + + return $this; + } + + /** + * Emit data and reset some status. + * + * @param string + * @param mixed + * + * @return boolean + */ + public function emit(string $event, $data = null): bool + { + $fds = $this->getFds(); + $assigned = !empty($this->getTo()); + + try { + if (empty($fds) && $assigned) { + return false; + } + + $result = $this->server->task([ + 'action' => static::PUSH_ACTION, + 'data' => [ + 'sender' => $this->getSender(), + 'descriptors' => $fds, + 'broadcast' => $this->isBroadcast(), + 'assigned' => $assigned, + 'payload' => $this->parser->encode($event, $data), + ], + ]); + + return $result !== false; + } finally { + $this->reset(); + } + } + + /** + * Close current connection. + * + * @param integer + * + * @return boolean + */ + public function close(int $fd = null) + { + return $this->server->close($fd ?: $this->getSender()); + } + + /** + * Set sender fd. + * + * @param integer + * + * @return $this + */ + public function setSender(int $fd) + { + Context::setData('websocket._sender', $fd); + + return $this; + } + + /** + * Get current sender fd. + */ + public function getSender() + { + return Context::getData('websocket._sender'); + } + + /** + * Get all fds we're going to push data to. + */ + protected function getFds() + { + $to = $this->getTo(); + $fds = array_filter($to, function ($value) { + return is_integer($value); + }); + $rooms = array_diff($to, $fds); + + foreach ($rooms as $room) { + $clients = $this->room->getClients($room); + // fallback fd with wrong type back to fds array + if (empty($clients) && is_numeric($room)) { + $fds[] = $room; + } else { + $fds = array_merge($fds, $clients); + } + } + + return array_values(array_unique($fds)); + } + + protected function reset() + { + Context::removeData("websocket._to"); + Context::removeData('websocket._broadcast'); + } +} diff --git a/vendor/topthink/think-swoole/src/command/RpcInterface.php b/vendor/topthink/think-swoole/src/command/RpcInterface.php new file mode 100644 index 0000000..612ab7f --- /dev/null +++ b/vendor/topthink/think-swoole/src/command/RpcInterface.php @@ -0,0 +1,79 @@ +setName('rpc:interface') + ->setDescription('Generate Rpc Service Interfaces'); + } + + public function handle() + { + go(function () { + $clients = $this->app->config->get('swoole.rpc.client', []); + + $file = new PhpFile; + $file->addComment('This file is auto-generated.'); + $file->setStrictTypes(); + + foreach ($clients as $name => $config) { + + $client = new Client($config['host'], $config['port']); + + $response = $client->sendAndRecv(Dispatcher::ACTION_INTERFACE); + + $parserClass = Arr::get($config, 'parser', JsonParser::class); + /** @var ParserInterface $parser */ + $parser = new $parserClass; + + $result = $parser->decodeResponse($response); + + if ($result instanceof Error) { + throw new RpcResponseException($result); + } + + $namespace = $file->addNamespace("rpc\\contract\\${name}"); + foreach ($result as $interface => $methods) { + $class = $namespace->addInterface($interface); + $class->addConstant("RPC", $name); + + foreach ($methods as $methodName => ['parameters' => $parameters, 'returnType' => $returnType, 'comment' => $comment]) { + $method = $class->addMethod($methodName) + ->setVisibility(ClassType::VISIBILITY_PUBLIC) + ->setComment(Helpers::unformatDocComment($comment)) + ->setReturnType($returnType); + + foreach ($parameters as $parameter) { + + $param = $method->addParameter($parameter['name']) + ->setTypeHint($parameter['type']); + + if (array_key_exists("default", $parameter)) { + $param->setDefaultValue($parameter['default']); + } + } + } + } + } + + file_put_contents($this->app->getBasePath() . 'rpc.php', $file); + + $this->output->writeln('Succeed!'); + }); + } +} diff --git a/vendor/topthink/think-swoole/src/command/Server.php b/vendor/topthink/think-swoole/src/command/Server.php new file mode 100644 index 0000000..61871aa --- /dev/null +++ b/vendor/topthink/think-swoole/src/command/Server.php @@ -0,0 +1,154 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole\command; + +use think\console\Command; +use think\console\input\Argument; +use think\swoole\Manager; +use think\swoole\PidManager; + +/** + * Swoole HTTP 命令行,支持操作:start|stop|restart|reload + * 支持应用配置目录下的swoole.php文件进行参数配置 + */ +class Server extends Command +{ + + public function configure() + { + $this->setName('swoole') + ->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload", 'start') + ->setDescription('Swoole HTTP Server for ThinkPHP'); + } + + public function handle() + { + $this->checkEnvironment(); + + $action = $this->input->getArgument('action'); + + if (in_array($action, ['start', 'stop', 'reload', 'restart'])) { + $this->app->invokeMethod([$this, $action], [], true); + } else { + $this->output->writeln("Invalid argument action:{$action}, Expected start|stop|restart|reload ."); + } + } + + /** + * 检查环境 + */ + protected function checkEnvironment() + { + if (!extension_loaded('swoole')) { + $this->output->error('Can\'t detect Swoole extension installed.'); + + exit(1); + } + + if (!version_compare(swoole_version(), '4.3.1', 'ge')) { + $this->output->error('Your Swoole version must be higher than `4.3.1`.'); + + exit(1); + } + } + + /** + * 启动server + * @access protected + * @param Manager $manager + * @param PidManager $pidManager + * @return void + */ + protected function start(Manager $manager, PidManager $pidManager) + { + if ($pidManager->isRunning()) { + $this->output->writeln('swoole http server process is already running.'); + return; + } + + $this->output->writeln('Starting swoole http server...'); + + $host = $manager->getConfig('server.host'); + $port = $manager->getConfig('server.port'); + + $this->output->writeln("Swoole http server started: "); + $this->output->writeln('You can exit with `CTRL-C`'); + + $manager->run(); + } + + /** + * 柔性重启server + * @access protected + * @param PidManager $manager + * @return void + */ + protected function reload(PidManager $manager) + { + if (!$manager->isRunning()) { + $this->output->writeln('no swoole http server process running.'); + return; + } + + $this->output->writeln('Reloading swoole http server...'); + + if (!$manager->killProcess(SIGUSR1)) { + $this->output->error('> failure'); + + return; + } + + $this->output->writeln('> success'); + } + + /** + * 停止server + * @access protected + * @param PidManager $manager + * @return void + */ + protected function stop(PidManager $manager) + { + if (!$manager->isRunning()) { + $this->output->writeln('no swoole http server process running.'); + return; + } + + $this->output->writeln('Stopping swoole http server...'); + + $isRunning = $manager->killProcess(SIGTERM, 15); + + if ($isRunning) { + $this->output->error('Unable to stop the swoole_http_server process.'); + return; + } + + $this->output->writeln('> success'); + } + + /** + * 重启server + * @access protected + * @param Manager $manager + * @param PidManager $pidManager + * @return void + */ + protected function restart(Manager $manager, PidManager $pidManager) + { + if ($pidManager->isRunning()) { + $this->stop($pidManager); + } + + $this->start($manager, $pidManager); + } + +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithHttp.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithHttp.php new file mode 100644 index 0000000..e7be06d --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithHttp.php @@ -0,0 +1,122 @@ +runInSandbox(function (Http $http, Event $event, App $app) use ($args, $req, $res) { + $event->trigger('swoole.request', $args); + + $request = $this->prepareRequest($req); + try { + $response = $this->handleRequest($http, $request); + } catch (Throwable $e) { + $response = $this->app + ->make(Handle::class) + ->render($request, $e); + } + + $this->sendResponse($res, $response, $app->cookie); + }); + } + + protected function handleRequest(Http $http, $request) + { + $level = ob_get_level(); + ob_start(); + + $response = $http->run($request); + + $content = $response->getContent(); + + if (ob_get_level() == 0) { + ob_start(); + } + + $http->end($response); + + if (ob_get_length() > 0) { + $response->content(ob_get_contents() . $content); + } + + while (ob_get_level() > $level) { + ob_end_clean(); + } + + return $response; + } + + protected function prepareRequest(Request $req) + { + $header = $req->header ?: []; + $server = $req->server ?: []; + + foreach ($header as $key => $value) { + $server["http_" . str_replace('-', '_', $key)] = $value; + } + + // 重新实例化请求对象 处理swoole请求数据 + /** @var \think\Request $request */ + $request = $this->app->make('request', [], true); + + return $request->withHeader($header) + ->withServer($server) + ->withGet($req->get ?: []) + ->withPost($req->post ?: []) + ->withCookie($req->cookie ?: []) + ->withInput($req->rawContent()) + ->withFiles($req->files ?: []) + ->setBaseUrl($req->server['request_uri']) + ->setUrl($req->server['request_uri'] . (!empty($req->server['query_string']) ? '?' . $req->server['query_string'] : '')) + ->setPathinfo(ltrim($req->server['path_info'], '/')); + } + + protected function sendResponse(Response $res, \think\Response $response, Cookie $cookie) + { + // 发送Header + foreach ($response->getHeader() as $key => $val) { + $res->header($key, $val); + } + + // 发送状态码 + $res->status($response->getCode()); + + foreach ($cookie->getCookie() as $name => $val) { + list($value, $expire, $option) = $val; + + $res->cookie($name, $value, $expire, $option['path'], $option['domain'], $option['secure'] ? true : false, $option['httponly'] ? true : false); + } + + $content = $response->getContent(); + + $res->end($content); + } +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithPool.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithPool.php new file mode 100644 index 0000000..bec6377 --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithPool.php @@ -0,0 +1,78 @@ +pools[$name])) { + $this->pools[$name] = new Channel($this->getPoolMaxActive($name)); + } + return $this->pools[$name]; + } + + protected function getPoolConnection($name) + { + $pool = $this->getPool($name); + + if (!isset($this->connectionCount[$name])) { + $this->connectionCount[$name] = 0; + } + + if ($this->connectionCount[$name] < $this->getPoolMaxActive($name)) { + //新建 + $connection = $this->createPoolConnection($name); + $this->connectionCount[$name]++; + } else { + $connection = $pool->pop($this->getPoolMaxWaitTime($name)); + + if ($connection === false) { + throw new RuntimeException(sprintf( + 'Borrow the connection timeout in %.2f(s), connections in pool: %d, all connections: %d', + $this->getPoolMaxWaitTime($name), + $pool->length(), + $this->connectionCount[$name] ?? 0 + )); + } + } + + return $this->buildPoolConnection($connection, $pool); + } + + abstract protected function buildPoolConnection($connection, Channel $pool); + + abstract protected function createPoolConnection(string $name); + + abstract protected function getPoolMaxActive($name): int; + + abstract protected function getPoolMaxWaitTime($name): int; + + public function __destruct() + { + foreach ($this->pools as $pool) { + while (true) { + if ($pool->isEmpty()) { + break; + } + $handler = $pool->pop(0.001); + unset($handler); + } + $pool->close(); + } + } +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithPoolConnector.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithPoolConnector.php new file mode 100644 index 0000000..e0002af --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithPoolConnector.php @@ -0,0 +1,42 @@ +handler = $handler; + $this->pool = $pool; + } + + public function __call($method, $arguments) + { + return $this->handler->{$method}(...$arguments); + } + + public function release() + { + if (!$this->release) { + return; + } + $this->release = false; + + if (!$this->pool->isFull()) { + $this->pool->push($this->handler, 0.001); + } + } + + public function __destruct() + { + $this->release(); + } +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithRpc.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithRpc.php new file mode 100644 index 0000000..6aac47c --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithRpc.php @@ -0,0 +1,122 @@ +isRpcServer = $this->getConfig('rpc.server.enable', false)) { + $host = $this->getConfig('server.host'); + $port = $this->getConfig('rpc.server.port', 9000); + + $rpcServer = $this->getServer()->addlistener($host, $port, SWOOLE_SOCK_TCP); + + $rpcServer->set([ + 'open_eof_check' => true, + 'open_eof_split' => true, + 'package_eof' => ParserInterface::EOF, + ]); + + $this->setRpcServerListeners($rpcServer); + } + + $this->onEvent('workerStart', function () { + if ($this->isRpcServer) { + $this->bindRpcParser(); + $this->bindRpcDispatcher(); + } + $this->bindRpcClientPool(); + }); + } + + /** + * @param Server\Port $server + */ + protected function setRpcServerListeners($server) + { + foreach ($this->rpcEvents as $event) { + $listener = Str::camel("on_rpc_$event"); + $callback = method_exists($this, $listener) ? [$this, $listener] : function () use ($event) { + $this->triggerEvent("rpc." . $event, func_get_args()); + }; + + $server->on($event, $callback); + } + } + + protected function bindRpcClientPool() + { + if (!empty($clients = $this->getConfig('rpc.client'))) { + $this->app->instance(Pool::class, new Pool($clients)); + //引入rpc接口文件 + if (file_exists($rpc = $this->app->getBasePath() . 'rpc.php')) { + include_once $rpc; + } + } + } + + protected function bindRpcDispatcher() + { + $services = $this->getConfig('rpc.server.services', []); + + $this->app->make(Dispatcher::class, [$services]); + } + + protected function bindRpcParser() + { + $parserClass = $this->getConfig('rpc.server.parser', JsonParser::class); + + $this->app->bind(ParserInterface::class, $parserClass); + $this->app->make(ParserInterface::class); + } + + public function onRpcConnect(Server $server, int $fd, int $reactorId) + { + $args = func_get_args(); + $this->runInSandbox(function (Event $event, Dispatcher $dispatcher) use ($fd, $server, $args) { + $event->trigger("swoole.rpc.Connect", $args); + }, $fd, true); + } + + public function onRpcReceive(Server $server, $fd, $reactorId, $data) + { + $data = rtrim($data, ParserInterface::EOF); + $this->runInSandbox(function (Dispatcher $dispatcher) use ($fd, $data, $server) { + $dispatcher->dispatch($fd, $data); + }, $fd, true); + } + + public function onRpcClose(Server $server, int $fd, int $reactorId) + { + $args = func_get_args(); + $this->runInSandbox(function (Event $event) use ($args) { + $event->trigger("swoole.rpc.Close", $args); + }, $fd); + } + +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithServer.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithServer.php new file mode 100644 index 0000000..bf9e9cd --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithServer.php @@ -0,0 +1,97 @@ +setProcessName('master process'); + $this->pidManager->create($this->getServer()->master_pid, $this->getServer()->manager_pid ?? 0); + + $this->triggerEvent("start", func_get_args()); + } + + /** + * The listener of "managerStart" event. + * + * @return void + */ + public function onManagerStart() + { + $this->setProcessName('manager process'); + $this->triggerEvent("managerStart", func_get_args()); + } + + /** + * "onWorkerStart" listener. + * + * @param \Swoole\Http\Server|mixed $server + * + * @throws Exception + */ + public function onWorkerStart($server) + { + Runtime::enableCoroutine($this->getConfig('coroutine.enable', true), $this->getConfig('coroutine.flags', SWOOLE_HOOK_ALL)); + + $this->clearCache(); + + $this->setProcessName($server->taskworker ? 'task process' : 'worker process'); + + $this->prepareApplication(); + + $this->bindSwooleTable(); + + $this->triggerEvent("workerStart"); + } + + /** + * Set onTask listener. + * + * @param mixed $server + * @param Task $task + */ + public function onTask($server, Task $task) + { + $this->runInSandbox(function (Event $event) use ($task) { + $event->trigger('swoole.task', $task); + }); + } + + /** + * Set onFinish listener. + * + * @param mixed $server + * @param string $taskId + * @param mixed $data + */ + public function onFinish($server, $taskId, $data) + { + $this->triggerEvent('finish', func_get_args()); + } + + /** + * Set onShutdown listener. + */ + public function onShutdown() + { + $this->triggerEvent('shutdown'); + $this->pidManager->remove(); + } + +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithSwooleTable.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithSwooleTable.php new file mode 100644 index 0000000..f5ecd06 --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithSwooleTable.php @@ -0,0 +1,72 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole\concerns; + +use Swoole\Table as SwooleTable; +use think\App; +use think\Container; +use think\swoole\Table; + +/** + * Trait InteractsWithSwooleTable + * + * @property Container $container + * @property App $app + */ +trait InteractsWithSwooleTable +{ + + /** + * @var Table + */ + protected $currentTable; + + /** + * Register customized swoole talbes. + */ + protected function createTables() + { + $this->currentTable = new Table(); + $this->registerTables(); + } + + /** + * Register user-defined swoole tables. + */ + protected function registerTables() + { + $tables = $this->container->make('config')->get('swoole.tables', []); + + foreach ($tables as $key => $value) { + $table = new SwooleTable($value['size']); + $columns = $value['columns'] ?? []; + foreach ($columns as $column) { + if (isset($column['size'])) { + $table->column($column['name'], $column['type'], $column['size']); + } else { + $table->column($column['name'], $column['type']); + } + } + $table->create(); + + $this->currentTable->add($key, $table); + } + } + + /** + * Bind swoole table to app container. + */ + protected function bindSwooleTable() + { + $this->app->instance(Table::class, $this->currentTable); + } +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithWebsocket.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithWebsocket.php new file mode 100644 index 0000000..e81a859 --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithWebsocket.php @@ -0,0 +1,238 @@ +app->make(Websocket::class); + $websocket->setSender($req->fd); + + $this->runInSandbox(function (Event $event, HandlerInterface $handler) use ($req) { + $request = $this->prepareRequest($req); + + if (!$handler->onOpen($req->fd, $request)) { + $event->trigger("swoole.websocket.Connect", $request); + } + }, $req->fd, true); + } + + /** + * "onMessage" listener. + * + * @param Server $server + * @param Frame $frame + */ + public function onMessage($server, $frame) + { + /** @var Websocket $websocket */ + $websocket = $this->app->make(Websocket::class); + $websocket->setSender($frame->fd); + + $this->runInSandbox(function (Event $event, ParserInterface $parser, HandlerInterface $handler) use ($frame) { + if (!$handler->onMessage($frame)) { + $payload = $parser->decode($frame); + + ['event' => $name, 'data' => $data] = $payload; + $name = Str::studly($name); + if (!in_array($name, ['Close', 'Connect'])) { + $event->trigger("swoole.websocket." . $name, $data); + } + } + }, $frame->fd, true); + } + + /** + * "onClose" listener. + * + * @param Server $server + * @param int $fd + * @param int $reactorId + */ + public function onClose($server, $fd, $reactorId) + { + if (!$this->isWebsocketServer($fd) || !$server instanceof Server) { + return; + } + + /** @var Websocket $websocket */ + $websocket = $this->app->make(Websocket::class); + $websocket->setSender($fd); + + $this->runInSandbox(function (Event $event, HandlerInterface $handler) use ($websocket, $fd, $reactorId) { + try { + if (!$handler->onClose($fd, $reactorId)) { + $event->trigger("swoole.websocket.Close"); + } + } finally { + // leave all rooms + $websocket->leave(); + } + }, $fd); + } + + /** + * Prepare settings if websocket is enabled. + */ + protected function prepareWebsocket() + { + if (!$this->isWebsocketServer = $this->getConfig('websocket.enable', false)) { + return; + } + + $this->events = array_merge($this->events ?? [], $this->wsEvents); + + $this->prepareWebsocketRoom(); + + $this->onEvent('workerStart', function () { + $this->bindWebsocketRoom(); + $this->bindWebsocketParser(); + $this->bindWebsocketHandler(); + $this->prepareWebsocketListener(); + }); + } + + /** + * Check if it's a websocket fd. + * + * @param int $fd + * + * @return bool + */ + protected function isWebsocketServer(int $fd): bool + { + return $this->getServer()->getClientInfo($fd)['websocket_status'] ?? false; + } + + /** + * Prepare websocket room. + */ + protected function prepareWebsocketRoom() + { + // create room instance and initialize + $this->websocketRoom = $this->container->make(Room::class); + $this->websocketRoom->prepare(); + } + + protected function prepareWebsocketListener() + { + $listeners = $this->getConfig('websocket.listen', []); + + foreach ($listeners as $event => $listener) { + $this->app->event->listen('swoole.websocket.' . Str::studly($event), $listener); + } + + $subscribers = $this->getConfig('websocket.subscribe', []); + + foreach ($subscribers as $subscriber) { + $this->app->event->observe($subscriber, 'swoole.websocket.'); + } + + //消息推送任务 + $this->app->event->listen('swoole.task', function (Task $task, App $app) { + if ($this->isWebsocketPushPayload($task->data)) { + $pusher = $app->make(Pusher::class, $task->data['data']); + $pusher->push(); + } + }); + } + + /** + * Prepare websocket handler for onOpen and onClose callback. + * + * @throws \Exception + */ + protected function bindWebsocketHandler() + { + $handlerClass = $this->getConfig('websocket.handler', Handler::class); + + $this->app->bind(HandlerInterface::class, $handlerClass); + + $this->app->make(HandlerInterface::class); + } + + protected function bindWebsocketParser() + { + $parserClass = $this->getConfig('websocket.parser', SocketioParser::class); + + $this->app->bind(ParserInterface::class, $parserClass); + + $this->app->make(ParserInterface::class); + } + + /** + * Bind room instance to app container. + */ + protected function bindWebsocketRoom(): void + { + $this->app->instance(Room::class, $this->websocketRoom); + } + + /** + * Indicates if the payload is websocket push. + * + * @param mixed $payload + * + * @return boolean + */ + public function isWebsocketPushPayload($payload): bool + { + if (!is_array($payload)) { + return false; + } + + return $this->isWebsocketServer + && ($payload['action'] ?? null) === Websocket::PUSH_ACTION + && array_key_exists('data', $payload); + } +} diff --git a/vendor/topthink/think-swoole/src/config/swoole.php b/vendor/topthink/think-swoole/src/config/swoole.php new file mode 100644 index 0000000..9dd4e39 --- /dev/null +++ b/vendor/topthink/think-swoole/src/config/swoole.php @@ -0,0 +1,94 @@ + [ + 'host' => env('SWOOLE_HOST', '127.0.0.1'), // 监听地址 + 'port' => env('SWOOLE_PORT', 80), // 监听端口 + 'mode' => SWOOLE_PROCESS, // 运行模式 默认为SWOOLE_PROCESS + 'sock_type' => SWOOLE_SOCK_TCP, // sock type 默认为SWOOLE_SOCK_TCP + 'options' => [ + 'pid_file' => runtime_path() . 'swoole.pid', + 'log_file' => runtime_path() . 'swoole.log', + 'daemonize' => false, + // Normally this value should be 1~4 times larger according to your cpu cores. + 'reactor_num' => swoole_cpu_num(), + 'worker_num' => swoole_cpu_num(), + 'task_worker_num' => swoole_cpu_num(), + 'task_enable_coroutine' => true, + 'task_max_request' => 3000, + 'enable_static_handler' => true, + 'document_root' => root_path('public'), + 'package_max_length' => 20 * 1024 * 1024, + 'buffer_output_size' => 10 * 1024 * 1024, + 'socket_buffer_size' => 128 * 1024 * 1024, + 'max_request' => 3000, + 'send_yield' => true, + ], + ], + 'websocket' => [ + 'enable' => false, + 'handler' => Handler::class, + 'parser' => Parser::class, + 'ping_interval' => 25000, + 'ping_timeout' => 60000, + 'room' => [ + 'type' => 'table', + 'table' => [ + 'room_rows' => 4096, + 'room_size' => 2048, + 'client_rows' => 8192, + 'client_size' => 2048, + ], + 'redis' => [ + + ], + ], + 'listen' => [], + 'subscribe' => [], + ], + 'rpc' => [ + 'server' => [ + 'enable' => false, + 'port' => 9000, + 'services' => [ + ], + ], + 'client' => [ + ], + ], + 'hot_update' => [ + 'enable' => env('APP_DEBUG', false), + 'name' => ['*.php'], + 'include' => [app_path()], + 'exclude' => [], + ], + //连接池 + 'pool' => [ + 'db' => [ + 'enable' => true, + 'max_active' => 3, + 'max_wait_time' => 5, + ], + 'cache' => [ + 'enable' => true, + 'max_active' => 3, + 'max_wait_time' => 5, + ], + ], + 'coroutine' => [ + 'enable' => true, + 'flags' => SWOOLE_HOOK_ALL, + ], + 'tables' => [], + //每个worker里需要预加载以共用的实例 + 'concretes' => [], + //重置器 + 'resetters' => [], + //每次请求前需要清空的实例 + 'instances' => [], + //每次请求前需要重新执行的服务 + 'services' => [], +]; diff --git a/vendor/topthink/think-swoole/src/contract/ResetterInterface.php b/vendor/topthink/think-swoole/src/contract/ResetterInterface.php new file mode 100644 index 0000000..1e39b2c --- /dev/null +++ b/vendor/topthink/think-swoole/src/contract/ResetterInterface.php @@ -0,0 +1,17 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole\contract\websocket; + +use Swoole\Websocket\Frame; +use think\Request; + +interface HandlerInterface +{ + /** + * "onOpen" listener. + * + * @param int $fd + * @param Request $request + */ + public function onOpen($fd, Request $request); + + /** + * "onMessage" listener. + * only triggered when event handler not found + * + * @param Frame $frame + */ + public function onMessage(Frame $frame); + + /** + * "onClose" listener. + * + * @param int $fd + * @param int $reactorId + */ + public function onClose($fd, $reactorId); +} diff --git a/vendor/topthink/think-swoole/src/contract/websocket/ParserInterface.php b/vendor/topthink/think-swoole/src/contract/websocket/ParserInterface.php new file mode 100644 index 0000000..651d464 --- /dev/null +++ b/vendor/topthink/think-swoole/src/contract/websocket/ParserInterface.php @@ -0,0 +1,29 @@ +getMessage(), $error->getCode()); + $this->error = $error; + } + + public function getError() + { + return $this->error; + } +} diff --git a/vendor/topthink/think-swoole/src/facade/Server.php b/vendor/topthink/think-swoole/src/facade/Server.php new file mode 100644 index 0000000..6f54a18 --- /dev/null +++ b/vendor/topthink/think-swoole/src/facade/Server.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole\facade; + +use think\Facade; + +class Server extends Facade +{ + protected static function getFacadeClass() + { + return 'swoole.server'; + } +} diff --git a/vendor/topthink/think-swoole/src/helpers.php b/vendor/topthink/think-swoole/src/helpers.php new file mode 100644 index 0000000..ec7d82b --- /dev/null +++ b/vendor/topthink/think-swoole/src/helpers.php @@ -0,0 +1,20 @@ +cloner = new VarCloner(); + $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + } + + public function handle(Request $request, Closure $next) + { + $prevHandler = VarDumper::setHandler(function ($var) { + $dumper = new HtmlDumper(); + $dumper->dump($this->cloner->cloneVar($var)); + }); + $response = $next($request); + VarDumper::setHandler($prevHandler); + return $response; + } +} diff --git a/vendor/topthink/think-swoole/src/pool/Cache.php b/vendor/topthink/think-swoole/src/pool/Cache.php new file mode 100644 index 0000000..63a2968 --- /dev/null +++ b/vendor/topthink/think-swoole/src/pool/Cache.php @@ -0,0 +1,47 @@ +app->config->get('swoole.pool.cache.max_active', 3); + } + + protected function getPoolMaxWaitTime($name): int + { + return $this->app->config->get('swoole.pool.cache.max_wait_time', 3); + } + + /** + * 获取驱动实例 + * @param null|string $name + * @return mixed + */ + protected function driver(string $name = null) + { + $name = $name ?: $this->getDefaultDriver(); + + return Context::rememberData("cache.store.{$name}", function () use ($name) { + return $this->getPoolConnection($name); + }); + } + + protected function buildPoolConnection($connection, Channel $pool) + { + return new Store($connection, $pool); + } + + protected function createPoolConnection(string $name) + { + return $this->createDriver($name); + } +} diff --git a/vendor/topthink/think-swoole/src/pool/Db.php b/vendor/topthink/think-swoole/src/pool/Db.php new file mode 100644 index 0000000..dc9c145 --- /dev/null +++ b/vendor/topthink/think-swoole/src/pool/Db.php @@ -0,0 +1,72 @@ +config->get('swoole.pool.db.max_active', 3); + } + + protected function getPoolMaxWaitTime($name): int + { + return $this->config->get('swoole.pool.db.max_wait_time', 3); + } + + /** + * 创建数据库连接实例 + * @access protected + * @param string|null $name 连接标识 + * @param bool $force 强制重新连接 + * @return ConnectionInterface + */ + protected function instance(string $name = null, bool $force = false): ConnectionInterface + { + if (empty($name)) { + $name = $this->getConfig('default', 'mysql'); + } + + if ($force) { + return $this->createConnection($name); + } + + return Context::rememberData("db.connection.{$name}", function () use ($name) { + return $this->getPoolConnection($name); + }); + } + + protected function buildPoolConnection($connection, Channel $pool) + { + return new Connection($connection, $pool); + } + + protected function createPoolConnection(string $name) + { + return $this->createConnection($name); + } + + protected function getConnectionConfig(string $name): array + { + $config = parent::getConnectionConfig($name); + + //打开断线重连 + $config['break_reconnect'] = true; + return $config; + } + +} diff --git a/vendor/topthink/think-swoole/src/pool/cache/Store.php b/vendor/topthink/think-swoole/src/pool/cache/Store.php new file mode 100644 index 0000000..e2d504b --- /dev/null +++ b/vendor/topthink/think-swoole/src/pool/cache/Store.php @@ -0,0 +1,10 @@ +handler->getQueryClass(); + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 接参数 + * @param integer $linkNum 连接序号 + * @return mixed + */ + public function connect(array $config = [], $linkNum = 0) + { + return $this->handler->connect($config, $linkNum); + } + + /** + * 设置当前的数据库Db对象 + * @access public + * @param DbManager $db + * @return void + */ + public function setDb(DbManager $db) + { + $this->handler->setDb($db); + } + + /** + * 设置当前的缓存对象 + * @access public + * @param CacheInterface $cache + * @return void + */ + public function setCache(CacheInterface $cache) + { + $this->handler->setCache($cache); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig(string $config = '') + { + return $this->handler->getConfig($config); + } + + /** + * 关闭数据库(或者重新连接) + * @access public + */ + public function close() + { + return $this->handler->close(); + } + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + */ + public function find(BaseQuery $query): array + { + return $this->handler->find($query); + } + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + */ + public function select(BaseQuery $query): array + { + return $this->handler->select($query); + } + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + */ + public function insert(BaseQuery $query, bool $getLastInsID = false) + { + return $this->handler->insert($query, $getLastInsID); + } + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param mixed $dataSet 数据集 + * @return integer + * @throws \Exception + * @throws \Throwable + */ + public function insertAll(BaseQuery $query, array $dataSet = []): int + { + return $this->handler->insertAll($query, $dataSet); + } + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return integer + */ + public function update(BaseQuery $query): int + { + return $this->handler->update($query); + } + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + */ + public function delete(BaseQuery $query): int + { + return $this->handler->delete($query); + } + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null) + { + return $this->handler->value($query, $field, $default); + } + + /** + * 得到某个列的数组 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $column 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, string $column, string $key = ''): array + { + return $this->handler->column($query, $column, $key); + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws \Throwable + */ + public function transaction(callable $callback) + { + return $this->handler->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + * @throws \Exception + */ + public function startTrans() + { + $this->handler->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + */ + public function commit() + { + $this->handler->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + */ + public function rollback() + { + $this->handler->commit(); + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->handler->getLastSql(); + } + +} diff --git a/vendor/topthink/think-swoole/src/resetters/ClearInstances.php b/vendor/topthink/think-swoole/src/resetters/ClearInstances.php new file mode 100644 index 0000000..bca390a --- /dev/null +++ b/vendor/topthink/think-swoole/src/resetters/ClearInstances.php @@ -0,0 +1,23 @@ +getConfig()->get('swoole.instances', [])); + + foreach ($instances as $instance) { + $app->delete($instance); + } + + return $app; + } +} diff --git a/vendor/topthink/think-swoole/src/resetters/ResetConfig.php b/vendor/topthink/think-swoole/src/resetters/ResetConfig.php new file mode 100644 index 0000000..6dec744 --- /dev/null +++ b/vendor/topthink/think-swoole/src/resetters/ResetConfig.php @@ -0,0 +1,18 @@ +instance('config', clone $sandbox->getConfig()); + + return $app; + } +} diff --git a/vendor/topthink/think-swoole/src/resetters/ResetEvent.php b/vendor/topthink/think-swoole/src/resetters/ResetEvent.php new file mode 100644 index 0000000..1c09106 --- /dev/null +++ b/vendor/topthink/think-swoole/src/resetters/ResetEvent.php @@ -0,0 +1,32 @@ +getEvent(); + + $closure = function () use ($app) { + $this->app = $app; + }; + + $resetEvent = $closure->bindTo($event, $event); + $resetEvent(); + + $app->instance('event', $event); + + return $app; + } +} diff --git a/vendor/topthink/think-swoole/src/resetters/ResetService.php b/vendor/topthink/think-swoole/src/resetters/ResetService.php new file mode 100644 index 0000000..e01a41b --- /dev/null +++ b/vendor/topthink/think-swoole/src/resetters/ResetService.php @@ -0,0 +1,45 @@ +getServices() as $service) { + $this->rebindServiceContainer($app, $service); + if (method_exists($service, 'register')) { + $service->register(); + } + if (method_exists($service, 'boot')) { + $app->invoke([$service, 'boot']); + } + } + } + + protected function rebindServiceContainer($app, $service) + { + $closure = function () use ($app) { + $this->app = $app; + }; + + $resetService = $closure->bindTo($service, $service); + $resetService(); + } +} diff --git a/vendor/topthink/think-swoole/src/rpc/Error.php b/vendor/topthink/think-swoole/src/rpc/Error.php new file mode 100644 index 0000000..73d57fe --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/Error.php @@ -0,0 +1,72 @@ +code = $code; + $instance->message = $message; + $instance->data = $data; + + return $instance; + } + + /** + * @return int + */ + public function getCode(): int + { + return $this->code; + } + + /** + * @return string + */ + public function getMessage(): string + { + return $this->message; + } + + /** + * @return mixed + */ + public function getData() + { + return $this->data; + } + + public function jsonSerialize() + { + return [ + 'code' => $this->code, + 'message' => $this->message, + 'data' => $this->data, + ]; + } +} diff --git a/vendor/topthink/think-swoole/src/rpc/JsonParser.php b/vendor/topthink/think-swoole/src/rpc/JsonParser.php new file mode 100644 index 0000000..f8c16c0 --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/JsonParser.php @@ -0,0 +1,124 @@ +getInterface(); + $methodName = $protocol->getMethod(); + + $method = $interface . self::DELIMITER . $methodName; + $data = [ + 'jsonrpc' => self::VERSION, + 'method' => $method, + 'params' => $protocol->getParams(), + 'id' => '', + ]; + + $string = json_encode($data, JSON_UNESCAPED_UNICODE); + + return $string; + } + + /** + * @param string $string + * + * @return Protocol + */ + public function decode(string $string): Protocol + { + $data = json_decode($string, true); + + $error = json_last_error(); + if ($error != JSON_ERROR_NONE) { + throw new Exception( + sprintf('Data(%s) is not json format!', $string) + ); + } + + $method = $data['method'] ?? ''; + $params = $data['params'] ?? []; + + if (empty($method)) { + throw new Exception( + sprintf('Method(%s) cant not be empty!', $string) + ); + } + + $methodAry = explode(self::DELIMITER, $method); + if (count($methodAry) < 2) { + throw new Exception( + sprintf('Method(%s) is bad format!', $method) + ); + } + + [$interfaceClass, $methodName] = $methodAry; + + if (empty($interfaceClass) || empty($methodName)) { + throw new Exception( + sprintf('Interface(%s) or Method(%s) can not be empty!', $interfaceClass, $method) + ); + } + + return Protocol::make($interfaceClass, $methodName, $params); + } + + /** + * @param string $string + * + * @return mixed + */ + public function decodeResponse(string $string) + { + $data = json_decode($string, true); + + if (array_key_exists('result', $data)) { + return $data['result']; + } + + $code = $data['error']['code'] ?? 0; + $message = $data['error']['message'] ?? ''; + $data = $data['error']['data'] ?? null; + + return Error::make($code, $message, $data); + } + + /** + * @param mixed $result + * + * @return string + */ + public function encodeResponse($result): string + { + $data = [ + 'jsonrpc' => self::VERSION, + ]; + + if ($result instanceof Error) { + $data['error'] = $result; + } else { + $data['result'] = $result; + } + + $string = json_encode($data); + + return $string; + } +} diff --git a/vendor/topthink/think-swoole/src/rpc/Protocol.php b/vendor/topthink/think-swoole/src/rpc/Protocol.php new file mode 100644 index 0000000..41c8150 --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/Protocol.php @@ -0,0 +1,67 @@ +interface = $interface; + $instance->method = $method; + $instance->params = $params; + + return $instance; + } + + /** + * @return string + */ + public function getInterface(): string + { + return $this->interface; + } + + /** + * @return string + */ + public function getMethod(): string + { + return $this->method; + } + + /** + * @return array + */ + public function getParams(): array + { + return $this->params; + } + +} diff --git a/vendor/topthink/think-swoole/src/rpc/client/Client.php b/vendor/topthink/think-swoole/src/rpc/client/Client.php new file mode 100644 index 0000000..f1b7703 --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/client/Client.php @@ -0,0 +1,87 @@ +host = $host; + $this->port = $port; + $this->timeout = $timeout; + $this->options = $options; + $this->connect(); + } + + public function sendAndRecv(string $data, bool $reconnect = false) + { + if ($reconnect) { + $this->connect(); + } + + try { + if (!$this->send($data)) { + throw new RpcClientException(swoole_strerror($this->handler->errCode), $this->handler->errCode); + } + + $result = $this->handler->recv(); + + if ($result === false || empty($result)) { + throw new RpcClientException(swoole_strerror($this->handler->errCode), $this->handler->errCode); + } + + return $result; + } catch (RpcClientException $e) { + if ($reconnect) { + throw $e; + } + return $this->sendAndRecv($data, true); + } + } + + public function send($data) + { + return $this->handler->send($data . ParserInterface::EOF); + } + + protected function connect() + { + $client = new \Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); + + $client->set([ + 'open_eof_check' => true, + 'open_eof_split' => true, + 'package_eof' => ParserInterface::EOF, + ]); + + if (!$client->connect($this->host, $this->port, $this->timeout)) { + throw new RpcClientException( + sprintf('Connect failed host=%s port=%d', $this->host, $this->port) + ); + } + + $this->handler = $client; + } + + public function __destruct() + { + if ($this->handler) { + $this->handler->close(); + } + } +} diff --git a/vendor/topthink/think-swoole/src/rpc/client/Connection.php b/vendor/topthink/think-swoole/src/rpc/client/Connection.php new file mode 100644 index 0000000..3339512 --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/client/Connection.php @@ -0,0 +1,15 @@ +clients = $clients; + } + + protected function getPoolMaxActive($name) + { + return $this->getClientConfig($name, 'max_active', 3); + } + + protected function getPoolMaxWaitTime($name) + { + return $this->getClientConfig($name, 'max_wait_time', 3); + } + + public function getClientConfig($client, $name, $default = null) + { + return Arr::get($this->clients, $client . "." . $name, $default); + } + + /** + * @param $name + * @return Connection + */ + public function connect($name) + { + return $this->getPoolConnection($name); + } + + protected function buildPoolConnection($client, Channel $pool) + { + return new Connection($client, $pool); + } + + protected function createPoolConnection(string $name) + { + $host = $this->getClientConfig($name, 'host', '127.0.0.1'); + $port = $this->getClientConfig($name, 'port', 9000); + $timeout = $this->getClientConfig($name, 'timeout', 0.5); + + return new Client($host, $port, $timeout); + } +} diff --git a/vendor/topthink/think-swoole/src/rpc/client/Proxy.php b/vendor/topthink/think-swoole/src/rpc/client/Proxy.php new file mode 100644 index 0000000..56df557 --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/client/Proxy.php @@ -0,0 +1,102 @@ +pool = $pool; + + $parserClass = $this->pool->getClientConfig($this->client, 'parser', JsonParser::class); + $this->parser = new $parserClass; + } + + protected function proxyCall($method, $params) + { + $protocol = Protocol::make($this->interface, $method, $params); + $data = $this->parser->encode($protocol); + + $client = $this->pool->connect($this->client); + + $response = $client->sendAndRecv($data); + + $client->release(); + + $result = $this->parser->decodeResponse($response); + + if ($result instanceof Error) { + throw new RpcResponseException($result); + } + + return $result; + } + + public static function getClassName($interface) + { + if (!interface_exists($interface)) { + throw new InvalidArgumentException( + sprintf('%s must be exist interface!', $interface) + ); + } + + $name = constant($interface . "::RPC"); + $proxyName = class_basename($interface) . "Service"; + $className = "rpc\\service\\${name}\\{$proxyName}"; + + if (!class_exists($className, false)) { + $file = new PhpFile; + $namespace = $file->addNamespace("rpc\\service\\${name}"); + $namespace->addUse(Proxy::class); + $namespace->addUse($interface); + + $class = $namespace->addClass($proxyName); + + $class->setExtends(Proxy::class); + $class->addImplement($interface); + $class->addProperty('client', $name); + $class->addProperty('interface', class_basename($interface)); + + $reflection = new ReflectionClass($interface); + + foreach ($reflection->getMethods() as $methodRef) { + $method = (new Factory)->fromMethodReflection($methodRef); + $method->setBody("return \$this->proxyCall('{$methodRef->getName()}', func_get_args());"); + $class->addMember($method); + } + + if (function_exists('eval')) { + eval($file); + } else { + $proxyFile = sprintf('%s/%s.php', sys_get_temp_dir(), $proxyName); + $result = file_put_contents($proxyFile, $file); + if ($result === false) { + throw new RuntimeException(sprintf('Proxy file(%s) generate fail', $proxyFile)); + } + require $proxyFile; + unlink($proxyFile); + } + } + return $className; + } +} diff --git a/vendor/topthink/think-swoole/src/rpc/server/Dispatcher.php b/vendor/topthink/think-swoole/src/rpc/server/Dispatcher.php new file mode 100644 index 0000000..cf591e0 --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/server/Dispatcher.php @@ -0,0 +1,169 @@ +app = $app; + $this->parser = $parser; + $this->server = $server; + $this->prepareServices($services); + } + + /** + * 获取服务接口 + * @param $services + * @throws \ReflectionException + */ + protected function prepareServices($services) + { + foreach ($services as $className) { + $reflectionClass = new ReflectionClass($className); + $interfaces = $reflectionClass->getInterfaceNames(); + + foreach ($interfaces as $interface) { + $this->services[class_basename($interface)] = [ + 'interface' => $interface, + 'class' => $className, + ]; + } + } + } + + /** + * 获取接口信息 + * @return array + */ + protected function getInterfaces() + { + $interfaces = []; + foreach ($this->services as $key => ['interface' => $interface]) { + $interfaces[$key] = $this->getMethods($interface); + } + return $interfaces; + } + + protected function getMethods($interface) + { + $methods = []; + + $reflection = new ReflectionClass($interface); + foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + $returnType = $method->getReturnType(); + if ($returnType instanceof ReflectionNamedType) { + $returnType = $returnType->getName(); + } + $methods[$method->getName()] = [ + 'parameters' => $this->getParameters($method), + 'returnType' => $returnType, + 'comment' => $method->getDocComment(), + ]; + } + return $methods; + } + + protected function getParameters(ReflectionMethod $method) + { + $parameters = []; + foreach ($method->getParameters() as $parameter) { + $type = $parameter->getType(); + if ($type instanceof ReflectionNamedType) { + $type = $type->getName(); + } + $param = [ + 'name' => $parameter->getName(), + 'type' => $type, + ]; + + if ($parameter->isOptional()) { + $param['default'] = $parameter->getDefaultValue(); + } + + $parameters[] = $param; + } + return $parameters; + } + + /** + * 调度 + * @param int $fd + * @param string $data + */ + public function dispatch(int $fd, string $data) + { + try { + if ($data === Dispatcher::ACTION_INTERFACE) { + $result = $this->getInterfaces(); + } else { + $protocol = $this->parser->decode($data); + + $interface = $protocol->getInterface(); + $method = $protocol->getMethod(); + $params = $protocol->getParams(); + $service = $this->services[$interface] ?? null; + if (empty($service)) { + throw new Exception( + sprintf('Service %s is not founded!', $interface), + self::INVALID_REQUEST + ); + } + + $result = $this->app->invoke([$this->app->make($service['class']), $method], $params); + } + } catch (Throwable | Exception $e) { + $result = Error::make($e->getCode(), $e->getMessage()); + } + + $data = $this->parser->encodeResponse($result); + + $this->server->send($fd, $data . ParserInterface::EOF); + } + +} diff --git a/vendor/topthink/think-swoole/src/websocket/Pusher.php b/vendor/topthink/think-swoole/src/websocket/Pusher.php new file mode 100644 index 0000000..d16c972 --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/Pusher.php @@ -0,0 +1,202 @@ +sender = $sender; + $this->descriptors = $descriptors; + $this->broadcast = $broadcast; + $this->assigned = $assigned; + $this->payload = $payload; + $this->server = $server; + } + + /** + * @return int + */ + public function getSender(): int + { + return $this->sender; + } + + /** + * @return array + */ + public function getDescriptors(): array + { + return $this->descriptors; + } + + /** + * @param int $descriptor + * + * @return self + */ + public function addDescriptor($descriptor): self + { + return $this->addDescriptors([$descriptor]); + } + + /** + * @param array $descriptors + * + * @return self + */ + public function addDescriptors(array $descriptors): self + { + $this->descriptors = array_values( + array_unique( + array_merge($this->descriptors, $descriptors) + ) + ); + + return $this; + } + + /** + * @param int $descriptor + * + * @return bool + */ + public function hasDescriptor(int $descriptor): bool + { + return in_array($descriptor, $this->descriptors); + } + + /** + * @return bool + */ + public function isBroadcast(): bool + { + return $this->broadcast; + } + + /** + * @return bool + */ + public function isAssigned(): bool + { + return $this->assigned; + } + + /** + * @return string + */ + public function getPayload(): string + { + return $this->payload; + } + + /** + * @return bool + */ + public function shouldBroadcast(): bool + { + return $this->broadcast && empty($this->descriptors) && !$this->assigned; + } + + /** + * Returns all descriptors that are websocket + * + * @return array + */ + protected function getWebsocketConnections(): array + { + return array_filter(iterator_to_array($this->server->connections), function ($fd) { + return (bool) $this->server->getClientInfo($fd)['websocket_status'] ?? false; + }); + } + + /** + * @param int $fd + * + * @return bool + */ + protected function shouldPushToDescriptor(int $fd): bool + { + if (!$this->server->exist($fd)) { + return false; + } + + return $this->broadcast ? $this->sender !== (int) $fd : true; + } + + /** + * Push message to related descriptors + * @return void + */ + public function push(): void + { + // attach sender if not broadcast + if (!$this->broadcast && $this->sender && !$this->hasDescriptor($this->sender)) { + $this->addDescriptor($this->sender); + } + + // check if to broadcast to other clients + if ($this->shouldBroadcast()) { + $this->addDescriptors($this->getWebsocketConnections()); + } + + // push message to designated fds + foreach ($this->descriptors as $descriptor) { + if ($this->shouldPushToDescriptor($descriptor)) { + $this->server->push($descriptor, $this->payload); + } + } + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/Room.php b/vendor/topthink/think-swoole/src/websocket/Room.php new file mode 100644 index 0000000..ce005ac --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/Room.php @@ -0,0 +1,30 @@ +app->config->get("swoole.websocket.room.{$name}", []); + } + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->app->config->get('swoole.websocket.room.type', 'table'); + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/SimpleParser.php b/vendor/topthink/think-swoole/src/websocket/SimpleParser.php new file mode 100644 index 0000000..2d219b5 --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/SimpleParser.php @@ -0,0 +1,46 @@ + $event, + 'data' => $data, + ] + ); + } + + /** + * Input message on websocket connected. + * Define and return event name and payload data here. + * + * @param Frame $frame + * + * @return array + */ + public function decode($frame) + { + $data = json_decode($frame->data, true); + + return [ + 'event' => $data['event'] ?? null, + 'data' => $data['data'] ?? null, + ]; + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/room/Redis.php b/vendor/topthink/think-swoole/src/websocket/room/Redis.php new file mode 100644 index 0000000..9adc69a --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/room/Redis.php @@ -0,0 +1,231 @@ +config = $config; + + if ($prefix = Arr::get($this->config, 'prefix')) { + $this->prefix = $prefix; + } + } + + /** + * @return RoomInterface + */ + public function prepare(): RoomInterface + { + $this->cleanRooms(); + + //关闭redis + $this->redis->close(); + $this->redis = null; + return $this; + } + + /** + * Set redis client. + * + */ + protected function getRedis() + { + if (!$this->redis) { + $host = Arr::get($this->config, 'host', '127.0.0.1'); + $port = Arr::get($this->config, 'port', 6379); + + $this->redis = new PHPRedis(); + $this->redis->pconnect($host, $port); + } + return $this->redis; + } + + /** + * Add multiple socket fds to a room. + * + * @param int fd + * @param array|string rooms + */ + public function add(int $fd, $rooms) + { + $rooms = is_array($rooms) ? $rooms : [$rooms]; + + $this->addValue($fd, $rooms, RoomInterface::DESCRIPTORS_KEY); + + foreach ($rooms as $room) { + $this->addValue($room, [$fd], RoomInterface::ROOMS_KEY); + } + } + + /** + * Delete multiple socket fds from a room. + * + * @param int fd + * @param array|string rooms + */ + public function delete(int $fd, $rooms) + { + $rooms = is_array($rooms) ? $rooms : [$rooms]; + $rooms = count($rooms) ? $rooms : $this->getRooms($fd); + + $this->removeValue($fd, $rooms, RoomInterface::DESCRIPTORS_KEY); + + foreach ($rooms as $room) { + $this->removeValue($room, [$fd], RoomInterface::ROOMS_KEY); + } + } + + /** + * Add value to redis. + * + * @param $key + * @param array $values + * @param string $table + * + * @return $this + */ + protected function addValue($key, array $values, string $table) + { + $this->checkTable($table); + $redisKey = $this->getKey($key, $table); + + $pipe = $this->getRedis()->multi(PHPRedis::PIPELINE); + + foreach ($values as $value) { + $pipe->sadd($redisKey, $value); + } + + $pipe->exec(); + + return $this; + } + + /** + * Remove value from reddis. + * + * @param $key + * @param array $values + * @param string $table + * + * @return $this + */ + protected function removeValue($key, array $values, string $table) + { + $this->checkTable($table); + $redisKey = $this->getKey($key, $table); + + $pipe = $this->getRedis()->multi(PHPRedis::PIPELINE); + + foreach ($values as $value) { + $pipe->srem($redisKey, $value); + } + + $pipe->exec(); + + return $this; + } + + /** + * Get all sockets by a room key. + * + * @param string room + * + * @return array + */ + public function getClients(string $room) + { + return $this->getValue($room, RoomInterface::ROOMS_KEY) ?? []; + } + + /** + * Get all rooms by a fd. + * + * @param int fd + * + * @return array + */ + public function getRooms(int $fd) + { + return $this->getValue($fd, RoomInterface::DESCRIPTORS_KEY) ?? []; + } + + /** + * Check table for rooms and descriptors. + * + * @param string $table + */ + protected function checkTable(string $table) + { + if (!in_array($table, [RoomInterface::ROOMS_KEY, RoomInterface::DESCRIPTORS_KEY])) { + throw new InvalidArgumentException("Invalid table name: `{$table}`."); + } + } + + /** + * Get value. + * + * @param string $key + * @param string $table + * + * @return array + */ + protected function getValue(string $key, string $table) + { + $this->checkTable($table); + + return $this->getRedis()->smembers($this->getKey($key, $table)); + } + + /** + * Get key. + * + * @param string $key + * @param string $table + * + * @return string + */ + protected function getKey(string $key, string $table) + { + return "{$this->prefix}{$table}:{$key}"; + } + + /** + * Clean all rooms. + */ + protected function cleanRooms(): void + { + if (count($keys = $this->getRedis()->keys("{$this->prefix}*"))) { + $this->getRedis()->del($keys); + } + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/room/Table.php b/vendor/topthink/think-swoole/src/websocket/room/Table.php new file mode 100644 index 0000000..3294cca --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/room/Table.php @@ -0,0 +1,220 @@ + 4096, + 'room_size' => 2048, + 'client_rows' => 8192, + 'client_size' => 2048, + ]; + + /** + * @var SwooleTable + */ + protected $rooms; + + /** + * @var SwooleTable + */ + protected $fds; + + /** + * TableRoom constructor. + * + * @param array $config + */ + public function __construct(array $config) + { + $this->config = array_merge($this->config, $config); + } + + /** + * Do some init stuffs before workers started. + * + * @return RoomInterface + */ + public function prepare(): RoomInterface + { + $this->initRoomsTable(); + $this->initFdsTable(); + + return $this; + } + + /** + * Add multiple socket fds to a room. + * + * @param int fd + * @param array|string rooms + */ + public function add(int $fd, $roomNames) + { + $rooms = $this->getRooms($fd); + $roomNames = is_array($roomNames) ? $roomNames : [$roomNames]; + + foreach ($roomNames as $room) { + $fds = $this->getClients($room); + + if (in_array($fd, $fds)) { + continue; + } + + $fds[] = $fd; + $rooms[] = $room; + + $this->setClients($room, $fds); + } + + $this->setRooms($fd, $rooms); + } + + /** + * Delete multiple socket fds from a room. + * + * @param int fd + * @param array|string rooms + */ + public function delete(int $fd, $roomNames = []) + { + $allRooms = $this->getRooms($fd); + $roomNames = is_array($roomNames) ? $roomNames : [$roomNames]; + $rooms = count($roomNames) ? $roomNames : $allRooms; + + $removeRooms = []; + foreach ($rooms as $room) { + $fds = $this->getClients($room); + + if (!in_array($fd, $fds)) { + continue; + } + + $this->setClients($room, array_values(array_diff($fds, [$fd]))); + $removeRooms[] = $room; + } + + $this->setRooms($fd, collect($allRooms)->diff($removeRooms)->values()->toArray()); + } + + /** + * Get all sockets by a room key. + * + * @param string room + * + * @return array + */ + public function getClients(string $room) + { + return $this->getValue($room, RoomInterface::ROOMS_KEY) ?? []; + } + + /** + * Get all rooms by a fd. + * + * @param int fd + * + * @return array + */ + public function getRooms(int $fd) + { + return $this->getValue($fd, RoomInterface::DESCRIPTORS_KEY) ?? []; + } + + /** + * @param string $room + * @param array $fds + * + * @return $this + */ + protected function setClients(string $room, array $fds) + { + return $this->setValue($room, $fds, RoomInterface::ROOMS_KEY); + } + + /** + * @param int $fd + * @param array $rooms + * + * @return $this + */ + protected function setRooms(int $fd, array $rooms) + { + return $this->setValue($fd, $rooms, RoomInterface::DESCRIPTORS_KEY); + } + + /** + * Init rooms table + */ + protected function initRoomsTable(): void + { + $this->rooms = new SwooleTable($this->config['room_rows']); + $this->rooms->column('value', SwooleTable::TYPE_STRING, $this->config['room_size']); + $this->rooms->create(); + } + + /** + * Init descriptors table + */ + protected function initFdsTable() + { + $this->fds = new SwooleTable($this->config['client_rows']); + $this->fds->column('value', SwooleTable::TYPE_STRING, $this->config['client_size']); + $this->fds->create(); + } + + /** + * Set value to table + * + * @param $key + * @param array $value + * @param string $table + * + * @return $this + */ + public function setValue($key, array $value, string $table) + { + $this->checkTable($table); + + $this->$table->set($key, ['value' => json_encode($value)]); + + return $this; + } + + /** + * Get value from table + * + * @param string $key + * @param string $table + * + * @return array|mixed + */ + public function getValue(string $key, string $table) + { + $this->checkTable($table); + + $value = $this->$table->get($key); + + return $value ? json_decode($value['value'], true) : []; + } + + /** + * Check table for exists + * + * @param string $table + */ + protected function checkTable(string $table) + { + if (!property_exists($this, $table) || !$this->$table instanceof SwooleTable) { + throw new InvalidArgumentException("Invalid table name: `{$table}`."); + } + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/socketio/Controller.php b/vendor/topthink/think-swoole/src/websocket/socketio/Controller.php new file mode 100644 index 0000000..f244cc2 --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/socketio/Controller.php @@ -0,0 +1,54 @@ +param('transport'), $this->transports)) { + return json( + [ + 'code' => 0, + 'message' => 'Transport unknown', + ], + 400 + ); + } + + if ($request->has('sid')) { + $response = response('1:6'); + } else { + $sid = base64_encode(uniqid()); + $payload = json_encode( + [ + 'sid' => $sid, + 'upgrades' => ['websocket'], + 'pingInterval' => $config->get('swoole.websocket.ping_interval'), + 'pingTimeout' => $config->get('swoole.websocket.ping_timeout'), + ] + ); + $cookie->set('io', $sid); + $response = response('97:0' . $payload . '2:40'); + } + + return $response->contentType('text/plain'); + } + + public function reject(Request $request) + { + return json( + [ + 'code' => 3, + 'message' => 'Bad request', + ], + 400 + ); + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/socketio/Handler.php b/vendor/topthink/think-swoole/src/websocket/socketio/Handler.php new file mode 100644 index 0000000..546366b --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/socketio/Handler.php @@ -0,0 +1,98 @@ +server = $server; + $this->config = $config; + } + + /** + * "onOpen" listener. + * + * @param int $fd + * @param Request $request + */ + public function onOpen($fd, Request $request) + { + if (!$request->param('sid')) { + $payload = json_encode( + [ + 'sid' => base64_encode(uniqid()), + 'upgrades' => [], + 'pingInterval' => $this->config->get('swoole.websocket.ping_interval'), + 'pingTimeout' => $this->config->get('swoole.websocket.ping_timeout'), + ] + ); + $initPayload = Packet::OPEN . $payload; + $connectPayload = Packet::MESSAGE . Packet::CONNECT; + + $this->server->push($fd, $initPayload); + $this->server->push($fd, $connectPayload); + } + } + + /** + * "onMessage" listener. + * only triggered when event handler not found + * + * @param Frame $frame + * @return bool + */ + public function onMessage(Frame $frame) + { + $packet = $frame->data; + if (Packet::getPayload($packet)) { + return false; + } + + $this->checkHeartbeat($frame->fd, $packet); + + return true; + } + + /** + * "onClose" listener. + * + * @param int $fd + * @param int $reactorId + */ + public function onClose($fd, $reactorId) + { + return; + } + + protected function checkHeartbeat($fd, $packet) + { + $packetLength = strlen($packet); + $payload = ''; + + if ($isPing = Packet::isSocketType($packet, 'ping')) { + $payload .= Packet::PONG; + } + + if ($isPing && $packetLength > 1) { + $payload .= substr($packet, 1, $packetLength - 1); + } + + if ($isPing) { + $this->server->push($fd, $payload); + } + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/socketio/Packet.php b/vendor/topthink/think-swoole/src/websocket/socketio/Packet.php new file mode 100644 index 0000000..b3812bb --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/socketio/Packet.php @@ -0,0 +1,171 @@ + 'OPEN', + 1 => 'CLOSE', + 2 => 'PING', + 3 => 'PONG', + 4 => 'MESSAGE', + 5 => 'UPGRADE', + 6 => 'NOOP', + ]; + + /** + * Engine.io packet types. + */ + public static $engineTypes = [ + 0 => 'CONNECT', + 1 => 'DISCONNECT', + 2 => 'EVENT', + 3 => 'ACK', + 4 => 'ERROR', + 5 => 'BINARY_EVENT', + 6 => 'BINARY_ACK', + ]; + + /** + * Get socket packet type of a raw payload. + * + * @param string $packet + * + * @return int|null + */ + public static function getSocketType(string $packet) + { + $type = $packet[0] ?? null; + + if (!array_key_exists($type, static::$socketTypes)) { + return; + } + + return (int) $type; + } + + /** + * Get data packet from a raw payload. + * + * @param string $packet + * + * @return array|null + */ + public static function getPayload(string $packet) + { + $packet = trim($packet); + $start = strpos($packet, '['); + + if ($start === false || substr($packet, -1) !== ']') { + return; + } + + $data = substr($packet, $start, strlen($packet) - $start); + $data = json_decode($data, true); + + if (is_null($data)) { + return; + } + + return [ + 'event' => $data[0], + 'data' => $data[1] ?? null, + ]; + } + + /** + * Return if a socket packet belongs to specific type. + * + * @param $packet + * @param string $typeName + * + * @return bool + */ + public static function isSocketType($packet, string $typeName) + { + $type = array_search(strtoupper($typeName), static::$socketTypes); + + if ($type === false) { + return false; + } + + return static::getSocketType($packet) === $type; + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/socketio/Parser.php b/vendor/topthink/think-swoole/src/websocket/socketio/Parser.php new file mode 100644 index 0000000..7e2d936 --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/socketio/Parser.php @@ -0,0 +1,45 @@ +data); + + return [ + 'event' => $payload['event'] ?? null, + 'data' => $payload['data'] ?? null, + ]; + } +} diff --git a/vendor/topthink/think-template/.gitignore b/vendor/topthink/think-template/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/vendor/topthink/think-template/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/vendor/topthink/think-template/LICENSE b/vendor/topthink/think-template/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-template/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-template/README.md b/vendor/topthink/think-template/README.md new file mode 100644 index 0000000..1d19064 --- /dev/null +++ b/vendor/topthink/think-template/README.md @@ -0,0 +1,70 @@ +# ThinkTemplate + +基于XML和标签库的编译型模板引擎 + +## 主要特性 + +- 支持XML标签库和普通标签的混合定义; +- 支持直接使用PHP代码书写; +- 支持文件包含; +- 支持多级标签嵌套; +- 支持布局模板功能; +- 一次编译多次运行,编译和运行效率非常高; +- 模板文件和布局模板更新,自动更新模板缓存; +- 系统变量无需赋值直接输出; +- 支持多维数组的快速输出; +- 支持模板变量的默认值; +- 支持页面代码去除Html空白; +- 支持变量组合调节器和格式化功能; +- 允许定义模板禁用函数和禁用PHP语法; +- 通过标签库方式扩展; + +## 安装 + +~~~php +composer require topthink/think-template +~~~ + +## 用法示例 + + +~~~php + './template/', + 'cache_path' => './runtime/', + 'view_suffix' => 'html', +]; + +$template = new Template($config); +// 模板变量赋值 +$template->assign(['name' => 'think']); +// 读取模板文件渲染输出 +$template->fetch('index'); +// 完整模板文件渲染 +$template->fetch('./template/test.php'); +// 渲染内容输出 +$template->display($content); +~~~ + +支持静态调用 + +~~~ +use think\facade\Template; + +Template::config([ + 'view_path' => './template/', + 'cache_path' => './runtime/', + 'view_suffix' => 'html', +]); +Template::assign(['name' => 'think']); +Template::fetch('index',['name' => 'think']); +Template::display($content,['name' => 'think']); +~~~ + +详细用法参考[开发手册](https://www.kancloud.cn/manual/think-template/content) \ No newline at end of file diff --git a/vendor/topthink/think-template/composer.json b/vendor/topthink/think-template/composer.json new file mode 100644 index 0000000..f4e1205 --- /dev/null +++ b/vendor/topthink/think-template/composer.json @@ -0,0 +1,20 @@ +{ + "name": "topthink/think-template", + "description": "the php template engine", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "psr/simple-cache": "^1.0" + }, + "autoload": { + "psr-4": { + "think\\": "src" + } + } +} \ No newline at end of file diff --git a/vendor/topthink/think-template/src/Template.php b/vendor/topthink/think-template/src/Template.php new file mode 100644 index 0000000..84d35a5 --- /dev/null +++ b/vendor/topthink/think-template/src/Template.php @@ -0,0 +1,1320 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Exception; +use Psr\SimpleCache\CacheInterface; + +/** + * ThinkPHP分离出来的模板引擎 + * 支持XML标签和普通标签的模板解析 + * 编译型模板引擎 支持动态缓存 + */ +class Template +{ + /** + * 模板变量 + * @var array + */ + protected $data = []; + + /** + * 模板配置参数 + * @var array + */ + protected $config = [ + 'view_path' => '', // 模板路径 + 'view_suffix' => 'html', // 默认模板文件后缀 + 'view_depr' => DIRECTORY_SEPARATOR, + 'cache_path' => '', + 'cache_suffix' => 'php', // 默认模板缓存后缀 + 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数 + 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码 + 'tpl_begin' => '{', // 模板引擎普通标签开始标记 + 'tpl_end' => '}', // 模板引擎普通标签结束标记 + 'strip_space' => false, // 是否去除模板文件里面的html空格与换行 + 'tpl_cache' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'compile_type' => 'file', // 模板编译类型 + 'cache_prefix' => '', // 模板缓存前缀标识,可以动态改变 + 'cache_time' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒) + 'layout_on' => false, // 布局模板开关 + 'layout_name' => 'layout', // 布局模板入口文件 + 'layout_item' => '{__CONTENT__}', // 布局模板的内容替换标识 + 'taglib_begin' => '{', // 标签库标签开始标记 + 'taglib_end' => '}', // 标签库标签结束标记 + 'taglib_load' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测 + 'taglib_build_in' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序 + 'taglib_pre_load' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔 + 'display_cache' => false, // 模板渲染缓存 + 'cache_id' => '', // 模板缓存ID + 'tpl_replace_string' => [], + 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别 + 'default_filter' => 'htmlentities', // 默认过滤方法 用于普通标签输出 + ]; + + /** + * 保留内容信息 + * @var array + */ + private $literal = []; + + /** + * 扩展解析规则 + * @var array + */ + private $extend = []; + + /** + * 模板包含信息 + * @var array + */ + private $includeFile = []; + + /** + * 模板存储对象 + * @var object + */ + protected $storage; + + /** + * 查询缓存对象 + * @var CacheInterface + */ + protected $cache; + + /** + * 架构函数 + * @access public + * @param array $config + */ + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, $config); + + $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; + $this->config['taglib_end_origin'] = $this->config['taglib_end']; + + $this->config['taglib_begin'] = preg_quote($this->config['taglib_begin'], '/'); + $this->config['taglib_end'] = preg_quote($this->config['taglib_end'], '/'); + $this->config['tpl_begin'] = preg_quote($this->config['tpl_begin'], '/'); + $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/'); + + // 初始化模板编译存储器 + $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type); + + $this->storage = new $class(); + } + + /** + * 模板变量赋值 + * @access public + * @param array $vars 模板变量 + * @return $this + */ + public function assign(array $vars = []) + { + $this->data = array_merge($this->data, $vars); + return $this; + } + + /** + * 模板引擎参数赋值 + * @access public + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->config[$name] = $value; + } + + /** + * 设置缓存对象 + * @access public + * @param CacheInterface $cache 缓存对象 + * @return void + */ + public function setCache(CacheInterface $cache): void + { + $this->cache = $cache; + } + + /** + * 模板引擎配置 + * @access public + * @param array $config + * @return $this + */ + public function config(array $config) + { + $this->config = array_merge($this->config, $config); + return $this; + } + + /** + * 获取模板引擎配置项 + * @access public + * @param string $name + * @return mixed + */ + public function getConfig(string $name) + { + return $this->config[$name] ?? null; + } + + /** + * 模板变量获取 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function get(string $name = '') + { + if ('' == $name) { + return $this->data; + } + + $data = $this->data; + + foreach (explode('.', $name) as $key => $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + $data = null; + break; + } + } + + return $data; + } + + /** + * 扩展模板解析规则 + * @access public + * @param string $rule 解析规则 + * @param callable $callback 解析规则 + * @return void + */ + public function extend(string $rule, callable $callback = null): void + { + $this->extend[$rule] = $callback; + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @return void + */ + public function fetch(string $template, array $vars = []): void + { + if ($vars) { + $this->data = array_merge($this->data, $vars); + } + + if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) { + // 读取渲染缓存 + if ($this->cache->has($this->config['cache_id'])) { + echo $this->cache->get($this->config['cache_id']); + return; + } + } + + $template = $this->parseTemplateFile($template); + + if ($template) { + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); + + if (!$this->checkCache($cacheFile)) { + // 缓存无效 重新模板编译 + $content = file_get_contents($template); + $this->compiler($content, $cacheFile); + } + + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + + // 获取并清空缓存 + $content = ob_get_clean(); + + if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) { + // 缓存页面输出 + $this->cache->set($this->config['cache_id'], $content, $this->config['cache_time']); + } + + echo $content; + } + } + + /** + * 检查编译缓存是否存在 + * @access public + * @param string $cacheId 缓存的id + * @return boolean + */ + public function isCache(string $cacheId): bool + { + if ($cacheId && $this->cache && $this->config['display_cache']) { + // 缓存页面输出 + return $this->cache->has($cacheId); + } + + return false; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $vars 模板变量 + * @return void + */ + public function display(string $content, array $vars = []): void + { + if ($vars) { + $this->data = array_merge($this->data, $vars); + } + + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.'); + + if (!$this->checkCache($cacheFile)) { + // 缓存无效 模板编译 + $this->compiler($content, $cacheFile); + } + + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + } + + /** + * 设置布局 + * @access public + * @param mixed $name 布局模板名称 false 则关闭布局 + * @param string $replace 布局模板内容替换标识 + * @return $this + */ + public function layout($name, string $replace = '') + { + if (false === $name) { + // 关闭布局 + $this->config['layout_on'] = false; + } else { + // 开启布局 + $this->config['layout_on'] = true; + + // 名称必须为字符串 + if (is_string($name)) { + $this->config['layout_name'] = $name; + } + + if (!empty($replace)) { + $this->config['layout_item'] = $replace; + } + } + + return $this; + } + + /** + * 检查编译缓存是否有效 + * 如果无效则需要重新编译 + * @access private + * @param string $cacheFile 缓存文件名 + * @return bool + */ + private function checkCache(string $cacheFile): bool + { + if (!$this->config['tpl_cache'] || !is_file($cacheFile) || !$handle = @fopen($cacheFile, "r")) { + return false; + } + + // 读取第一行 + preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches); + + if (!isset($matches[1])) { + return false; + } + + $includeFile = unserialize($matches[1]); + + if (!is_array($includeFile)) { + return false; + } + + // 检查模板文件是否有更新 + foreach ($includeFile as $path => $time) { + if (is_file($path) && filemtime($path) > $time) { + // 模板文件如果有更新则缓存需要更新 + return false; + } + } + + // 检查编译存储是否有效 + return $this->storage->check($cacheFile, $this->config['cache_time']); + } + + /** + * 编译模板文件内容 + * @access private + * @param string $content 模板内容 + * @param string $cacheFile 缓存文件名 + * @return void + */ + private function compiler(string &$content, string $cacheFile): void + { + // 判断是否启用布局 + if ($this->config['layout_on']) { + if (false !== strpos($content, '{__NOLAYOUT__}')) { + // 可以单独定义不使用布局 + $content = str_replace('{__NOLAYOUT__}', '', $content); + } else { + // 读取布局模板 + $layoutFile = $this->parseTemplateFile($this->config['layout_name']); + + if ($layoutFile) { + // 替换布局的主体内容 + $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile)); + } + } + } else { + $content = str_replace('{__NOLAYOUT__}', '', $content); + } + + // 模板解析 + $this->parse($content); + + if ($this->config['strip_space']) { + /* 去除html空格与换行 */ + $find = ['~>\s+<~', '~>(\s+\n|\r)~']; + $replace = ['><', '>']; + $content = preg_replace($find, $replace, $content); + } + + // 优化生成的php代码 + $content = preg_replace('/\?>\s*<\?php\s(?!echo\b|\bend)/s', '', $content); + + // 模板过滤输出 + $replace = $this->config['tpl_replace_string']; + $content = str_replace(array_keys($replace), array_values($replace), $content); + + // 添加安全代码及模板引用记录 + $content = 'includeFile) . '*/ ?>' . "\n" . $content; + // 编译存储 + $this->storage->write($cacheFile, $content); + + $this->includeFile = []; + } + + /** + * 模板解析入口 + * 支持普通标签和TagLib解析 支持自定义标签库 + * @access public + * @param string $content 要解析的模板内容 + * @return void + */ + public function parse(string &$content): void + { + // 内容为空不解析 + if (empty($content)) { + return; + } + + // 替换literal标签内容 + $this->parseLiteral($content); + + // 解析继承 + $this->parseExtend($content); + + // 解析布局 + $this->parseLayout($content); + + // 检查include语法 + $this->parseInclude($content); + + // 替换包含文件中literal标签内容 + $this->parseLiteral($content); + + // 检查PHP语法 + $this->parsePhp($content); + + // 获取需要引入的标签库列表 + // 标签库只需要定义一次,允许引入多个一次 + // 一般放在文件的最前面 + // 格式: + // 当TAGLIB_LOAD配置为true时才会进行检测 + if ($this->config['taglib_load']) { + $tagLibs = $this->getIncludeTagLib($content); + + if (!empty($tagLibs)) { + // 对导入的TagLib进行解析 + foreach ($tagLibs as $tagLibName) { + $this->parseTagLib($tagLibName, $content); + } + } + } + + // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀 + if ($this->config['taglib_pre_load']) { + $tagLibs = explode(',', $this->config['taglib_pre_load']); + + foreach ($tagLibs as $tag) { + $this->parseTagLib($tag, $content); + } + } + + // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀 + $tagLibs = explode(',', $this->config['taglib_build_in']); + + foreach ($tagLibs as $tag) { + $this->parseTagLib($tag, $content, true); + } + + // 解析普通模板标签 {$tagName} + $this->parseTag($content); + + // 还原被替换的Literal标签 + $this->parseLiteral($content, true); + } + + /** + * 检查PHP语法 + * @access private + * @param string $content 要解析的模板内容 + * @return void + * @throws Exception + */ + private function parsePhp(string &$content): void + { + // 短标签的情况要将' . "\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); + } + } + + /** + * 解析模板中的include标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseInclude(string &$content): void + { + $regex = $this->getRegex('include'); + $func = function ($template) use (&$func, &$regex, &$content) { + if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array = $this->parseAttr($match[0]); + $file = $array['file']; + unset($array['file']); + + // 分析模板文件名并读取内容 + $parseStr = $this->parseTemplateName($file); + + foreach ($array as $k => $v) { + // 以$开头字符串转换成模板变量 + if (0 === strpos($v, '$')) { + $v = $this->get(substr($v, 1)); + } + + $parseStr = str_replace('[' . $k . ']', $v, $parseStr); + } + + $content = str_replace($match[0], $parseStr, $content); + // 再次对包含文件进行模板分析 + $func($parseStr); + } + unset($matches); + } + }; + + // 替换模板中的include标签 + $func($content); + } + + /** + * 解析模板中的extend标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseExtend(string &$content): void + { + $regex = $this->getRegex('extend'); + $array = $blocks = $baseBlocks = []; + $extend = ''; + + $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) { + if (preg_match($regex, $template, $matches)) { + if (!isset($array[$matches['name']])) { + $array[$matches['name']] = 1; + // 读取继承模板 + $extend = $this->parseTemplateName($matches['name']); + + // 递归检查继承 + $func($extend); + + // 取得block标签内容 + $blocks = array_merge($blocks, $this->parseBlock($template)); + + return; + } + } else { + // 取得顶层模板block标签内容 + $baseBlocks = $this->parseBlock($template, true); + + if (empty($extend)) { + // 无extend标签但有block标签的情况 + $extend = $template; + } + } + }; + + $func($content); + + if (!empty($extend)) { + if ($baseBlocks) { + $children = []; + foreach ($baseBlocks as $name => $val) { + $replace = $val['content']; + + if (!empty($children[$name])) { + // 如果包含有子block标签 + foreach ($children[$name] as $key) { + $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace); + } + } + + if (isset($blocks[$name])) { + // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖 + $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']); + + if (!empty($val['parent'])) { + // 如果不是最顶层的block标签 + $parent = $val['parent']; + + if (isset($blocks[$parent])) { + $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']); + } + + $blocks[$name]['content'] = $replace; + $children[$parent][] = $name; + + continue; + } + } elseif (!empty($val['parent'])) { + // 如果子标签没有被继承则用原值 + $children[$val['parent']][] = $name; + $blocks[$name] = $val; + } + + if (!$val['parent']) { + // 替换模板中的顶级block标签 + $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend); + } + } + } + + $content = $extend; + unset($blocks, $baseBlocks); + } + } + + /** + * 替换页面中的literal标签 + * @access private + * @param string $content 模板内容 + * @param boolean $restore 是否为还原 + * @return void + */ + private function parseLiteral(string &$content, bool $restore = false): void + { + $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal'); + + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + if (!$restore) { + $count = count($this->literal); + + // 替换literal标签 + foreach ($matches as $match) { + $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2])); + $content = str_replace($match[0], "", $content); + $count++; + } + } else { + // 还原literal标签 + foreach ($matches as $match) { + $content = str_replace($match[0], $this->literal[$match[1]], $content); + } + + // 清空literal记录 + $this->literal = []; + } + + unset($matches); + } + } + + /** + * 获取模板中的block标签 + * @access private + * @param string $content 模板内容 + * @param boolean $sort 是否排序 + * @return array + */ + private function parseBlock(string &$content, bool $sort = false): array + { + $regex = $this->getRegex('block'); + $result = []; + + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = $keys = []; + + foreach ($matches as $match) { + if (empty($match['name'][0])) { + if (count($right) > 0) { + $tag = array_pop($right); + $start = $tag['offset'] + strlen($tag['tag']); + $length = $match[0][1] - $start; + + $result[$tag['name']] = [ + 'begin' => $tag['tag'], + 'content' => substr($content, $start, $length), + 'end' => $match[0][0], + 'parent' => count($right) ? end($right)['name'] : '', + ]; + + $keys[$tag['name']] = $match[0][1]; + } + } else { + // 标签头压入栈 + $right[] = [ + 'name' => $match[2][0], + 'offset' => $match[0][1], + 'tag' => $match[0][0], + ]; + } + } + + unset($right, $matches); + + if ($sort) { + // 按block标签结束符在模板中的位置排序 + array_multisort($keys, $result); + } + } + + return $result; + } + + /** + * 搜索模板页面中包含的TagLib库 + * 并返回列表 + * @access private + * @param string $content 模板内容 + * @return array|null + */ + private function getIncludeTagLib(string &$content) + { + // 搜索是否有TagLib标签 + if (preg_match($this->getRegex('taglib'), $content, $matches)) { + // 替换TagLib标签 + $content = str_replace($matches[0], '', $content); + + return explode(',', $matches['name']); + } + } + + /** + * TagLib库解析 + * @access public + * @param string $tagLib 要解析的标签库 + * @param string $content 要解析的模板内容 + * @param boolean $hide 是否隐藏标签库前缀 + * @return void + */ + public function parseTagLib(string $tagLib, string &$content, bool $hide = false): void + { + if (false !== strpos($tagLib, '\\')) { + // 支持指定标签库的命名空间 + $className = $tagLib; + $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1); + } else { + $className = '\\think\\template\\taglib\\' . ucwords($tagLib); + } + + $tLib = new $className($this); + + $tLib->parseTag($content, $hide ? '' : $tagLib); + } + + /** + * 分析标签属性 + * @access public + * @param string $str 属性字符串 + * @param string $name 不为空时返回指定的属性名 + * @return array + */ + public function parseAttr(string $str, string $name = null): array + { + $regex = '/\s+(?>(?P[\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]; + } + + return $array; + } + + /** + * 模板标签解析 + * 格式: {TagName:args [|content] } + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseTag(string &$content): void + { + $regex = $this->getRegex('tag'); + + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $str = stripslashes($match[1]); + $flag = substr($str, 0, 1); + + switch ($flag) { + case '$': + // 解析模板变量 格式 {$varName} + // 是否带有?号 + if (false !== $pos = strpos($str, '?')) { + $array = preg_split('/([!=]={1,2}|(?<]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE); + $name = $array[0]; + + $this->parseVar($name); + //$this->parseVarFunction($name); + + $str = trim(substr($str, $pos + 1)); + $this->parseVar($str); + $first = substr($str, 0, 1); + + if (strpos($name, ')')) { + // $name为对象或是自动识别,或者含有函数 + if (isset($array[1])) { + $this->parseVar($array[2]); + $name .= $array[1] . $array[2]; + } + + switch ($first) { + case '?': + $this->parseVarFunction($name); + $str = ''; + break; + case '=': + $str = ''; + break; + default: + $str = ''; + } + } else { + if (isset($array[1])) { + $express = true; + $this->parseVar($array[2]); + $express = $name . $array[1] . $array[2]; + } else { + $express = false; + } + + if (in_array($first, ['?', '=', ':'])) { + $str = trim(substr($str, 1)); + if ('$' == substr($str, 0, 1)) { + $str = $this->parseVarFunction($str); + } + } + + // $name为数组 + switch ($first) { + case '?': + // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + break; + case '=': + // {$varname?='xxx'} $varname为真时才输出xxx + $str = ''; + break; + case ':': + // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + break; + default: + if (strpos($str, ':')) { + // {$varname ? 'a' : 'b'} $varname为真时输出a,否则输出b + $array = explode(':', $str, 2); + + $array[0] = '$' == substr(trim($array[0]), 0, 1) ? $this->parseVarFunction($array[0]) : $array[0]; + $array[1] = '$' == substr(trim($array[1]), 0, 1) ? $this->parseVarFunction($array[1]) : $array[1]; + + $str = implode(' : ', $array); + } + $str = ''; + } + } + } 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); + } + } + + /** + * 模板变量解析,支持使用函数 + * 格式: {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量数据 + * @return void + */ + public function parseVar(string &$varStr): void + { + $varStr = trim($varStr); + + if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) { + static $_varParseList = []; + + while ($matches[0]) { + $match = array_pop($matches[0]); + + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varParseList[$match[0]])) { + $parseStr = $_varParseList[$match[0]]; + } else { + if (strpos($match[0], '.')) { + $vars = explode('.', $match[0]); + $first = array_shift($vars); + + if (isset($this->extend[$first])) { + $callback = $this->extend[$first]; + $parseStr = $callback($vars); + } elseif ('$Request' == $first) { + // 输出请求变量 + $parseStr = $this->parseRequestVar($vars); + } elseif ('$Think' == $first) { + // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 + $parseStr = $this->parseThinkVar($vars); + } else { + switch ($this->config['tpl_var_identify']) { + case 'array': // 识别为数组 + $parseStr = $first . '[\'' . implode('\'][\'', $vars) . '\']'; + break; + case 'obj': // 识别为对象 + $parseStr = $first . '->' . implode('->', $vars); + break; + default: // 自动判断数组或对象 + $parseStr = '(is_array(' . $first . ')?' . $first . '[\'' . implode('\'][\'', $vars) . '\']:' . $first . '->' . implode('->', $vars) . ')'; + } + } + } else { + $parseStr = str_replace(':', '->', $match[0]); + } + + $_varParseList[$match[0]] = $parseStr; + } + + $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0])); + } + unset($matches); + } + } + + /** + * 对模板中使用了函数的变量进行解析 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量字符串 + * @param bool $autoescape 自动转义 + * @return string + */ + public function parseVarFunction(string &$varStr, bool $autoescape = true): string + { + if (!$autoescape && false === strpos($varStr, '|')) { + return $varStr; + } elseif ($autoescape && !preg_match('/\|(\s)?raw(\||\s)?/i', $varStr)) { + $varStr .= '|' . $this->config['default_filter']; + } + + static $_varFunctionList = []; + + $_key = md5($varStr); + + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varFunctionList[$_key])) { + $varStr = $_varFunctionList[$_key]; + } else { + $varArray = explode('|', $varStr); + + // 取得变量名称 + $name = trim(array_shift($varArray)); + + // 对变量使用函数 + $length = count($varArray); + + // 取得模板禁止使用函数列表 + $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']); + + for ($i = 0; $i < $length; $i++) { + $args = explode('=', $varArray[$i], 2); + + // 模板函数过滤 + $fun = trim($args[0]); + if (in_array($fun, $template_deny_funs)) { + continue; + } + + switch (strtolower($fun)) { + case 'raw': + break; + case 'date': + $name = 'date(' . $args[1] . ',!is_numeric(' . $name . ')? strtotime(' . $name . ') : ' . $name . ')'; + break; + case 'first': + $name = 'current(' . $name . ')'; + break; + case 'last': + $name = 'end(' . $name . ')'; + break; + case 'upper': + $name = 'strtoupper(' . $name . ')'; + break; + case 'lower': + $name = 'strtolower(' . $name . ')'; + break; + case 'format': + $name = 'sprintf(' . $args[1] . ',' . $name . ')'; + break; + case 'default': // 特殊模板函数 + if (false === strpos($name, '(')) { + $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; + } else { + $name = '(' . $name . ' ?: ' . $args[1] . ')'; + } + break; + default: // 通用模板函数 + if (isset($args[1])) { + if (strstr($args[1], '###')) { + $args[1] = str_replace('###', $name, $args[1]); + $name = "$fun($args[1])"; + } else { + $name = "$fun($name,$args[1])"; + } + } else { + if (!empty($args[0])) { + $name = "$fun($name)"; + } + } + } + } + + $_varFunctionList[$_key] = $name; + $varStr = $name; + } + return $varStr; + } + + /** + * 请求变量解析 + * 格式 以 $Request. 打头的变量属于请求变量 + * @access public + * @param array $vars 变量数组 + * @return string + */ + public function parseRequestVar(array $vars): string + { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + + switch ($type) { + case 'SERVER': + $parseStr = '$_SERVER[\'' . $param . '\']'; + break; + case 'GET': + $parseStr = '$_GET[\'' . $param . '\']'; + break; + case 'POST': + $parseStr = '$_POST[\'' . $param . '\']'; + break; + case 'COOKIE': + $parseStr = '$_COOKIE[\'' . $param . '\']'; + break; + case 'SESSION': + $parseStr = '$_SESSION[\'' . $param . '\']'; + break; + case 'ENV': + $parseStr = '$_ENV[\'' . $param . '\']'; + break; + case 'REQUEST': + $parseStr = '$_REQUEST[\'' . $param . '\']'; + break; + default: + $parseStr = '\'\''; + } + + return $parseStr; + } + + /** + * 特殊模板变量解析 + * 格式 以 $Think. 打头的变量属于特殊模板变量 + * @access public + * @param array $vars 变量数组 + * @return string + */ + public function parseThinkVar(array $vars): string + { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + + switch ($type) { + case 'CONST': + $parseStr = strtoupper($param); + break; + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; + break; + default: + $parseStr = defined($type) ? $type : '\'\''; + } + + return $parseStr; + } + + /** + * 分析加载的模板文件并读取内容 支持多个模板文件读取 + * @access private + * @param string $templateName 模板文件名 + * @return string + */ + private function parseTemplateName(string $templateName): string + { + $array = explode(',', $templateName); + $parseStr = ''; + + foreach ($array as $templateName) { + if (empty($templateName)) { + continue; + } + + if (0 === strpos($templateName, '$')) { + //支持加载变量文件名 + $templateName = $this->get(substr($templateName, 1)); + } + + $template = $this->parseTemplateFile($templateName); + + if ($template) { + // 获取模板文件内容 + $parseStr .= file_get_contents($template); + } + } + + return $parseStr; + } + + /** + * 解析模板文件名 + * @access private + * @param string $template 文件名 + * @return string + */ + private function parseTemplateFile(string $template): string + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $this->config['view_depr'], $template); + } else { + $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); + } + + $template = $this->config['view_path'] . $template . '.' . ltrim($this->config['view_suffix'], '.'); + } + + if (is_file($template)) { + // 记录模板文件的更新时间 + $this->includeFile[$template] = filemtime($template); + + return $template; + } + + throw new Exception('template not exists:' . $template); + } + + /** + * 按标签生成正则 + * @access private + * @param string $tagName 标签名 + * @return string + */ + private function getRegex(string $tagName): string + { + $regex = ''; + if ('tag' == $tagName) { + $begin = $this->config['tpl_begin']; + $end = $this->config['tpl_end']; + + if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { + $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; + } else { + $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end; + } + } else { + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + + switch ($tagName) { + case 'block': + if ($single) { + $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; + } else { + $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!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\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; + } else { + $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; + } + break; + } + } + + return '/' . $regex . '/is'; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['storage']); + + return $data; + } +} diff --git a/vendor/topthink/think-template/src/facade/Template.php b/vendor/topthink/think-template/src/facade/Template.php new file mode 100644 index 0000000..665a180 --- /dev/null +++ b/vendor/topthink/think-template/src/facade/Template.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +if (class_exists('think\Facade')) { + class Facade extends \think\Facade + {} +} else { + class Facade + { + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + protected static $instance; + + /** + * 获取当前Facade对应类名 + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 创建Facade实例 + * @static + * @access protected + * @return object + */ + protected static function createFacade() + { + $class = static::getFacadeClass() ?: 'think\Template'; + + if (static::$alwaysNewInstance) { + return new $class(); + } + + if (!self::$instance) { + self::$instance = new $class(); + } + + return self::$instance; + + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([static::createFacade(), $method], $params); + } + } +} + +/** + * @see \think\Template + * @mixin \think\Template + */ +class Template extends Facade +{ + protected static $alwaysNewInstance = true; + + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'think\Template'; + } +} diff --git a/vendor/topthink/think-template/src/template/TagLib.php b/vendor/topthink/think-template/src/template/TagLib.php new file mode 100644 index 0000000..f6c8fbb --- /dev/null +++ b/vendor/topthink/think-template/src/template/TagLib.php @@ -0,0 +1,349 @@ + +// +---------------------------------------------------------------------- + +namespace think\template; + +use Exception; +use think\Template; + +/** + * 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 Template $template 模板引擎对象 + */ + public function __construct(Template $template) + { + $this->tpl = $template; + } + + /** + * 按签标库替换页面中的标签 + * @access public + * @param string $content 模板内容 + * @param string $lib 标签库名 + * @return void + */ + public function parseTag(string &$content, string $lib = ''): void + { + $tags = []; + $lib = $lib ? strtolower($lib) . ':' : ''; + + foreach ($this->tags as $name => $val) { + $close = !isset($val['close']) || $val['close'] ? 1 : 0; + $tags[$close][$lib . $name] = $name; + if (isset($val['alias'])) { + // 别名设置 + $array = (array) $val['alias']; + foreach (explode(',', $array[0]) as $v) { + $tags[$close][$lib . $v] = $name; + } + } + } + + // 闭合标签 + if (!empty($tags[1])) { + $nodes = []; + $regex = $this->getRegex(array_keys($tags[1]), 1); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = []; + foreach ($matches as $match) { + if ('' == $match[1][0]) { + $name = strtolower($match[2][0]); + // 如果有没闭合的标签头则取出最后一个 + if (!empty($right[$name])) { + // $match[0][1]为标签结束符在模板中的位置 + $nodes[$match[0][1]] = [ + 'name' => $name, + 'begin' => array_pop($right[$name]), // 标签开始符 + 'end' => $match[0], // 标签结束符 + ]; + } + } else { + // 标签头压入栈 + $right[strtolower($match[1][0])][] = $match[0]; + } + } + unset($right, $matches); + // 按标签在模板中的位置从后向前排序 + krsort($nodes); + } + + $break = ''; + if ($nodes) { + $beginArray = []; + // 标签替换 从后向前 + foreach ($nodes as $pos => $node) { + // 对应的标签名 + $name = $tags[1][$node['name']]; + $alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : ''; + + // 解析标签属性 + $attrs = $this->parseAttr($node['begin'][0], $name, $alias); + $method = 'tag' . $name; + + // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾 + $replace = explode($break, $this->$method($attrs, $break)); + + if (count($replace) > 1) { + while ($beginArray) { + $begin = end($beginArray); + // 判断当前标签尾的位置是否在栈中最后一个标签头的后面,是则为子标签 + if ($node['end'][1] > $begin['pos']) { + break; + } else { + // 不为子标签时,取出栈中最后一个标签头 + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + // 替换标签尾部 + $content = substr_replace($content, $replace[1], $node['end'][1], strlen($node['end'][0])); + // 把标签头压入栈 + $beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]]; + } + } + + while ($beginArray) { + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + } + // 自闭合标签 + if (!empty($tags[0])) { + $regex = $this->getRegex(array_keys($tags[0]), 0); + $content = preg_replace_callback($regex, function ($matches) use (&$tags, &$lib) { + // 对应的标签名 + $name = $tags[0][strtolower($matches[1])]; + $alias = $lib . $name != $matches[1] ? ($lib ? strstr($matches[1], $lib) : $matches[1]) : ''; + // 解析标签属性 + $attrs = $this->parseAttr($matches[0], $name, $alias); + $method = 'tag' . $name; + return $this->$method($attrs, ''); + }, $content); + } + } + + /** + * 按标签生成正则 + * @access public + * @param array|string $tags 标签名 + * @param boolean $close 是否为闭合标签 + * @return string + */ + public function getRegex($tags, bool $close): string + { + $begin = $this->tpl->getConfig('taglib_begin'); + $end = $this->tpl->getConfig('taglib_end'); + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + $tagName = is_array($tags) ? implode('|', $tags) : $tags; + + if ($single) { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>[^' . $end . ']*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>[^' . $end . ']*)' . $end; + } + } else { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end; + } + } + + return '/' . $regex . '/is'; + } + + /** + * 分析标签属性 正则方式 + * @access public + * @param string $str 标签属性字符串 + * @param string $name 标签名 + * @param string $alias 别名 + * @return array + */ + public function parseAttr(string $str, string $name, string $alias = ''): array + { + $regex = '/\s+(?>(?P[\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->getConfig('taglib_begin_origin') . $name); + $_taglibs[$name][1] = strlen($this->tpl->getConfig('taglib_end_origin')); + } + $result['expression'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]); + // 清除自闭合标签尾部/ + $result['expression'] = rtrim($result['expression'], '/'); + $result['expression'] = trim($result['expression']); + } elseif (empty($this->tags[$name]) || !empty($this->tags[$name]['attr'])) { + throw new Exception('tag error:' . $name); + } + } + + return $result; + } + + /** + * 解析条件表达式 + * @access public + * @param string $condition 表达式标签内容 + * @return string + */ + public function parseCondition(string $condition): string + { + if (strpos($condition, ':')) { + $condition = ' ' . substr(strstr($condition, ':'), 1); + } + + $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); + $this->tpl->parseVar($condition); + + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar(string &$name): string + { + $flag = substr($name, 0, 1); + + if (':' == $flag) { + // 以:开头为函数调用,解析前去掉: + $name = substr($name, 1); + } elseif ('$' != $flag && preg_match('/[a-zA-Z_]/', $flag)) { + // XXX: 这句的写法可能还需要改进 + // 常量不需要解析 + if (defined($name)) { + return $name; + } + + // 不以$开头并且也不是常量,自动补上$前缀 + $name = '$' . $name; + } + + $this->tpl->parseVar($name); + $this->tpl->parseVarFunction($name, false); + + return $name; + } + + /** + * 获取标签列表 + * @access public + * @return array + */ + public function getTags(): array + { + return $this->tags; + } +} diff --git a/vendor/topthink/think-template/src/template/driver/File.php b/vendor/topthink/think-template/src/template/driver/File.php new file mode 100644 index 0000000..510d10a --- /dev/null +++ b/vendor/topthink/think-template/src/template/driver/File.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\driver; + +use Exception; + +class File +{ + protected $cacheFile; + + /** + * 写入编译缓存 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param string $content 缓存的内容 + * @return void + */ + public function write(string $cacheFile, string $content): void + { + // 检测模板目录 + $dir = dirname($cacheFile); + + if (!is_dir($dir)) { + mkdir($dir, 0755, true); + } + + // 生成模板缓存文件 + if (false === file_put_contents($cacheFile, $content)) { + throw new Exception('cache write error:' . $cacheFile, 11602); + } + } + + /** + * 读取编译编译 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param array $vars 变量数组 + * @return void + */ + public function read(string $cacheFile, array $vars = []): void + { + $this->cacheFile = $cacheFile; + + if (!empty($vars) && is_array($vars)) { + // 模板阵列变量分解成为独立变量 + extract($vars, EXTR_OVERWRITE); + } + + //载入模版缓存文件 + include $this->cacheFile; + } + + /** + * 检查编译缓存是否有效 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param int $cacheTime 缓存时间 + * @return bool + */ + public function check(string $cacheFile, int $cacheTime): bool + { + // 缓存文件不存在, 直接返回false + if (!file_exists($cacheFile)) { + return false; + } + + if (0 != $cacheTime && time() > filemtime($cacheFile) + $cacheTime) { + // 缓存是否在有效期 + return false; + } + + return true; + } +} diff --git a/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php b/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php new file mode 100644 index 0000000..dd88b32 --- /dev/null +++ b/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\exception; + +class TemplateNotFoundException extends \RuntimeException +{ + protected $template; + + public function __construct(string $message, string $template = '') + { + $this->message = $message; + $this->template = $template; + } + + /** + * 获取模板文件 + * @access public + * @return string + */ + public function getTemplate(): string + { + return $this->template; + } +} diff --git a/vendor/topthink/think-template/src/template/taglib/Cx.php b/vendor/topthink/think-template/src/template/taglib/Cx.php new file mode 100644 index 0000000..bccafc1 --- /dev/null +++ b/vendor/topthink/think-template/src/template/taglib/Cx.php @@ -0,0 +1,715 @@ + +// +---------------------------------------------------------------------- + +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(array $tag, string $content): string + { + $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 + */ + public function tagVolist(array $tag, string $content): string + { + $name = $tag['name']; + $id = $tag['id']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $key = !empty($tag['key']) ? $tag['key'] : 'i'; + $mod = isset($tag['mod']) ? $tag['mod'] : '2'; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + // 允许使用函数设定数据集 {$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 .= ''; + + return $parseStr; + } + + /** + * foreach标签解析 循环输出数据集 + * 格式: + * {foreach name="userList" id="user" key="key" index="i" mod="2" offset="3" length="5" empty=""} + * {user.username} + * {/foreach} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagForeach(array $tag, string $content): string + { + // 直接使用表达式 + if (!empty($tag['expression'])) { + $expression = ltrim(rtrim($tag['expression'], ')'), '('); + $expression = $this->autoBuildVar($expression); + $parseStr = ''; + $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 .= ''; + + return $parseStr; + } + + /** + * if标签解析 + * 格式: + * {if condition=" $a eq 1"} + * {elseif condition="$a eq 2" /} + * {else /} + * {/if} + * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagIf(array $tag, string $content): string + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * elseif标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagElseif(array $tag, string $content): string + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = ''; + + return $parseStr; + } + + /** + * else标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function tagElse(array $tag): string + { + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $value = isset($tag['expression']) ? $tag['expression'] : $tag['value']; + $flag = substr($value, 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $value = 'case ' . $value . ':'; + } elseif (strpos($value, '|')) { + $values = explode('|', $value); + $value = ''; + foreach ($values as $val) { + $value .= 'case "' . addslashes($val) . '":'; + } + } else { + $value = 'case "' . $value . '":'; + } + + $parseStr = '' . $content; + $isBreak = isset($tag['break']) ? $tag['break'] : ''; + + if ('' == $isBreak || $isBreak) { + $parseStr .= ''; + } + + return $parseStr; + } + + /** + * default标签解析 需要配合switch才有效 + * 使用: {default /}ddfdf + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function tagDefault(array $tag): string + { + $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(array $tag, string $content): string + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型 + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } else { + $value = '\'' . $value . '\''; + } + + switch ($type) { + case 'equal': + $type = 'eq'; + break; + case 'notequal': + $type = 'neq'; + break; + } + $type = $this->parseCondition(' ' . $type . ' '); + $parseStr = '' . $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(array $tag, string $content): string + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'in'; // 比较类型 + + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')'; + } else { + $value = '"' . $value . '"'; + $str = 'explode(\',\',' . $value . ')'; + } + + if ('between' == $type) { + $parseStr = '= $_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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty()))): ?>' . $content . ''; + + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * {defined name='TXT'}已定义{/defined} + * @access public + * @param array $tag + * @param string $content + * @return string + */ + public function tagDefined(array $tag, string $content): string + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * 判断是否没有定义了该常量 + * {notdefined name='TXT'}已定义{/notdefined} + * @access public + * @param array $tag + * @param string $content + * @return string + */ + public function tagNotdefined(array $tag, string $content): string + { + $name = $tag['name']; + $parseStr = '' . $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(array $tag, string $content): string + { + $file = isset($tag['file']) ? $tag['file'] : $tag['href']; + $type = isset($tag['type']) ? strtolower($tag['type']) : ''; + + $parseStr = ''; + $endStr = ''; + + // 判断是否存在加载条件 允许使用函数判断(默认为isset) + if (isset($tag['value'])) { + $name = $tag['value']; + $name = $this->autoBuildVar($name); + $name = 'isset(' . $name . ')'; + $parseStr .= ''; + $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(array $tag, string $content): string + { + $name = $this->autoBuildVar($tag['name']); + $flag = substr($tag['value'], 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + + $parseStr = ''; + + return $parseStr; + } + + /** + * define标签解析 + * 在模板中定义常量 支持变量赋值 + * 格式: {define name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefine(array $tag, string $content): string + { + $name = '\'' . $tag['name'] . '\''; + $flag = substr($tag['value'], 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + + $parseStr = ''; + + return $parseStr; + } + + /** + * for标签解析 + * 格式: + * {for start="" end="" comparison="" step="" name=""} + * content + * {/for} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFor(array $tag, string $content): string + { + //设置默认值 + $start = 0; + $end = 0; + $step = 1; + $comparison = 'lt'; + $name = 'i'; + $rand = rand(); //添加随机数,防止嵌套变量冲突 + + //获取属性 + foreach ($tag as $key => $value) { + $value = trim($value); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } + + switch ($key) { + case 'start': + $start = $value; + break; + case 'end': + $end = $value; + break; + case 'step': + $step = $value; + break; + case 'comparison': + $comparison = $value; + break; + case 'name': + $name = $value; + break; + } + } + + $parseStr = '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(array $tag, string $content): string + { + $url = isset($tag['link']) ? $tag['link'] : ''; + $vars = isset($tag['vars']) ? $tag['vars'] : ''; + $suffix = isset($tag['suffix']) ? $tag['suffix'] : 'true'; + $domain = isset($tag['domain']) ? $tag['domain'] : 'false'; + + return ''; + } + + /** + * function标签解析 匿名函数,可实现递归 + * 使用: + * {function name="func" vars="$data" call="$list" use="&$a,&$b"} + * {if is_array($data)} + * {foreach $data as $val} + * {~func($val) /} + * {/foreach} + * {else /} + * {$data} + * {/if} + * {/function} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFunction(array $tag, string $content): string + { + $name = !empty($tag['name']) ? $tag['name'] : 'func'; + $vars = !empty($tag['vars']) ? $tag['vars'] : ''; + $call = !empty($tag['call']) ? $tag['call'] : ''; + $use = ['&$' . $name]; + + if (!empty($tag['use'])) { + foreach (explode(',', $tag['use']) as $val) { + $use[] = '&' . ltrim(trim($val), '&'); + } + } + + $parseStr = '' . $content . '' : '?>'; + + return $parseStr; + } +} diff --git a/vendor/topthink/think-view/.gitignore b/vendor/topthink/think-view/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/vendor/topthink/think-view/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/vendor/topthink/think-view/LICENSE b/vendor/topthink/think-view/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-view/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-view/README.md b/vendor/topthink/think-view/README.md new file mode 100644 index 0000000..4e52def --- /dev/null +++ b/vendor/topthink/think-view/README.md @@ -0,0 +1,36 @@ +# think-view + +ThinkPHP6.0 Think-Template模板引擎驱动 + + +## 安装 + +~~~php +composer require topthink/think-view +~~~ + +## 用法示例 + +本扩展不能单独使用,依赖ThinkPHP6.0+ + +首先配置config目录下的template.php配置文件,然后可以按照下面的用法使用。 + +~~~php + +use think\facade\View; + +// 模板变量赋值和渲染输出 +View::assign(['name' => 'think']) + // 输出过滤 + ->filter(function($content){ + return str_replace('search', 'replace', $content); + }) + // 读取模板文件渲染输出 + ->fetch('index'); + + +// 或者使用助手函数 +view('index', ['name' => 'think']); +~~~ + +具体的模板引擎配置请参考think-template库。 \ No newline at end of file diff --git a/vendor/topthink/think-view/composer.json b/vendor/topthink/think-view/composer.json new file mode 100644 index 0000000..f4e6431 --- /dev/null +++ b/vendor/topthink/think-view/composer.json @@ -0,0 +1,20 @@ +{ + "name": "topthink/think-view", + "description": "thinkphp template driver", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "topthink/think-template": "^2.0" + }, + "autoload": { + "psr-4": { + "think\\view\\driver\\": "src" + } + } +} diff --git a/vendor/topthink/think-view/src/Think.php b/vendor/topthink/think-view/src/Think.php new file mode 100644 index 0000000..02be10f --- /dev/null +++ b/vendor/topthink/think-view/src/Think.php @@ -0,0 +1,263 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\view\driver; + +use think\App; +use think\helper\Str; +use think\Template; +use think\template\exception\TemplateNotFoundException; + +class Think +{ + // 模板引擎实例 + private $template; + private $app; + + // 模板引擎参数 + protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 视图目录名 + 'view_dir_name' => 'view', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'tpl_cache' => true, + ]; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + + $this->config = array_merge($this->config, (array) $config); + + if (empty($this->config['cache_path'])) { + $this->config['cache_path'] = $app->getRuntimePath() . 'temp' . DIRECTORY_SEPARATOR; + } + + $this->template = new Template($this->config); + $this->template->setCache($app->cache); + $this->template->extend('$Think', function (array $vars) { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + + switch ($type) { + case 'CONST': + $parseStr = strtoupper($param); + break; + case 'CONFIG': + $parseStr = 'config(\'' . $param . '\')'; + break; + case 'LANG': + $parseStr = 'lang(\'' . $param . '\')'; + break; + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->getConfig('tpl_begin'), '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->getConfig('tpl_end'), '\\') . '\''; + break; + default: + $parseStr = defined($type) ? $type : '\'\''; + } + + return $parseStr; + }); + + $this->template->extend('$Request', function (array $vars) { + // 获取Request请求对象参数 + $method = array_shift($vars); + if (!empty($vars)) { + $params = implode('.', $vars); + if ('true' != $params) { + $params = '\'' . $params . '\''; + } + } else { + $params = ''; + } + + return 'app(\'request\')->' . $method . '(' . $params . ')'; + }); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists(string $template): bool + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch(string $template, array $data = []): void + { + if (empty($this->config['view_path'])) { + $view = $this->config['view_dir_name']; + + if (is_dir($this->app->getAppPath() . $view)) { + $path = $this->app->getAppPath() . $view . DIRECTORY_SEPARATOR; + } else { + $appName = $this->app->http->getName(); + $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . ($appName ? $appName . DIRECTORY_SEPARATOR : ''); + } + + $this->config['view_path'] = $path; + $this->template->view_path = $path; + } + + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + + // 记录视图信息 + $this->app['log'] + ->record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); + + $this->template->fetch($template, $data); + } + + /** + * 渲染模板内容 + * @access public + * @param string $template 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display(string $template, array $data = []): void + { + $this->template->display($template, $data); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate(string $template): string + { + // 分析模板文件规则 + $request = $this->app['request']; + + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($app, $template) = explode('@', $template); + } + + if (isset($app)) { + $view = $this->config['view_dir_name']; + $viewPath = $this->app->getBasePath() . $app . DIRECTORY_SEPARATOR . $view . DIRECTORY_SEPARATOR; + + if (is_dir($viewPath)) { + $path = $viewPath; + } else { + $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . $app . DIRECTORY_SEPARATOR; + } + + $this->template->view_path = $path; + } else { + $path = $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = $request->controller(); + + if (strpos($controller, '.')) { + $pos = strrpos($controller, '.'); + $controller = substr($controller, 0, $pos) . '.' . Str::snake(substr($controller, $pos + 1)); + } else { + $controller = Str::snake($controller); + } + + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认模板渲染规则定位 + if (2 == $this->config['auto_rule']) { + $template = $request->action(true); + } elseif (3 == $this->config['auto_rule']) { + $template = $request->action(); + } else { + $template = Str::snake($request->action()); + } + + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置模板引擎 + * @access private + * @param array $config 参数 + * @return void + */ + public function config(array $config): void + { + $this->template->config($config); + $this->config = array_merge($this->config, $config); + } + + /** + * 获取模板引擎配置 + * @access public + * @param string $name 参数名 + * @return void + */ + public function getConfig(string $name) + { + return $this->template->getConfig($name); + } + + public function __call($method, $params) + { + return call_user_func_array([$this->template, $method], $params); + } +} diff --git a/view/massage/tcp/game_list copy.html b/view/massage/tcp/game_list copy.html new file mode 100644 index 0000000..4798e64 --- /dev/null +++ b/view/massage/tcp/game_list copy.html @@ -0,0 +1,105 @@ + + + + + + + + + 实时排行榜 + + + + +
    + + + + + + +
    车手1最快圈速刷圈日期
    + + +
    + + $value){ ?> + +
    +
    +
    +
    +
    + + + + +
    + + + + +
    + + + \ No newline at end of file diff --git a/view/massage/tcp/game_list.html b/view/massage/tcp/game_list.html new file mode 100644 index 0000000..3f3a587 --- /dev/null +++ b/view/massage/tcp/game_list.html @@ -0,0 +1,122 @@ + + + + + + + + + 实时排行榜 + + + + +
    + + + + + + +
    车手最快圈速刷圈日期
    + +
    + + $value){ ?> + +
    +
    +
    +
    +
    + + + + +
    + + + +
    + + + + \ No newline at end of file diff --git a/web/.babelrc b/web/.babelrc new file mode 100644 index 0000000..3a280ba --- /dev/null +++ b/web/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + ["env", { + "modules": false, + "targets": { + "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] + } + }], + "stage-2" + ], + "plugins": ["transform-vue-jsx", "transform-runtime"] +} diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100644 index 0000000..9d08a1a --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/web/.eslintignore b/web/.eslintignore new file mode 100644 index 0000000..e1fcc9c --- /dev/null +++ b/web/.eslintignore @@ -0,0 +1,4 @@ +/build/ +/config/ +/dist/ +/*.js diff --git a/web/.eslintrc.js b/web/.eslintrc.js new file mode 100644 index 0000000..22fdce8 --- /dev/null +++ b/web/.eslintrc.js @@ -0,0 +1,29 @@ +// https://eslint.org/docs/user-guide/configuring + +module.exports = { + root: true, + parserOptions: { + parser: 'babel-eslint' + }, + env: { + browser: true, + }, + extends: [ + // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention + // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. + 'plugin:vue/essential', + // https://github.com/standard/standard/blob/master/docs/RULES-en.md + 'standard' + ], + // required to lint *.vue files + plugins: [ + 'vue' + ], + // add your custom rules here + rules: { + // allow async-await + 'generator-star-spacing': 'off', + // allow debugger during development + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' + } +} diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..541a820 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,14 @@ +.DS_Store +node_modules/ +/dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln diff --git a/web/.postcssrc.js b/web/.postcssrc.js new file mode 100644 index 0000000..eee3e92 --- /dev/null +++ b/web/.postcssrc.js @@ -0,0 +1,10 @@ +// https://github.com/michael-ciniawsky/postcss-load-config + +module.exports = { + "plugins": { + "postcss-import": {}, + "postcss-url": {}, + // to edit target browsers: use "browserslist" field in package.json + "autoprefixer": {} + } +} diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..ea61862 --- /dev/null +++ b/web/README.md @@ -0,0 +1,21 @@ +# longbing_back + +> 独立后台 + +## Build Setup + +``` bash +# install dependencies +npm install + +# serve with hot reload at localhost:8080 +npm run dev + +# build for production with minification +npm run build + +# build for production and view the bundle analyzer report +npm run build --report +``` + +For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). diff --git a/web/build/build.js b/web/build/build.js new file mode 100644 index 0000000..8f2ad8a --- /dev/null +++ b/web/build/build.js @@ -0,0 +1,41 @@ +'use strict' +require('./check-versions')() + +process.env.NODE_ENV = 'production' + +const ora = require('ora') +const rm = require('rimraf') +const path = require('path') +const chalk = require('chalk') +const webpack = require('webpack') +const config = require('../config') +const webpackConfig = require('./webpack.prod.conf') + +const spinner = ora('building for production...') +spinner.start() + +rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { + if (err) throw err + webpack(webpackConfig, (err, stats) => { + spinner.stop() + if (err) throw err + process.stdout.write(stats.toString({ + colors: true, + modules: false, + children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. + chunks: false, + chunkModules: false + }) + '\n\n') + + if (stats.hasErrors()) { + console.log(chalk.red(' Build failed with errors.\n')) + process.exit(1) + } + + console.log(chalk.cyan(' Build complete.\n')) + console.log(chalk.yellow( + ' Tip: built files are meant to be served over an HTTP server.\n' + + ' Opening index.html over file:// won\'t work.\n' + )) + }) +}) diff --git a/web/build/check-versions.js b/web/build/check-versions.js new file mode 100644 index 0000000..3ef972a --- /dev/null +++ b/web/build/check-versions.js @@ -0,0 +1,54 @@ +'use strict' +const chalk = require('chalk') +const semver = require('semver') +const packageConfig = require('../package.json') +const shell = require('shelljs') + +function exec (cmd) { + return require('child_process').execSync(cmd).toString().trim() +} + +const versionRequirements = [ + { + name: 'node', + currentVersion: semver.clean(process.version), + versionRequirement: packageConfig.engines.node + } +] + +if (shell.which('npm')) { + versionRequirements.push({ + name: 'npm', + currentVersion: exec('npm --version'), + versionRequirement: packageConfig.engines.npm + }) +} + +module.exports = function () { + const warnings = [] + + for (let i = 0; i < versionRequirements.length; i++) { + const mod = versionRequirements[i] + + if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { + warnings.push(mod.name + ': ' + + chalk.red(mod.currentVersion) + ' should be ' + + chalk.green(mod.versionRequirement) + ) + } + } + + if (warnings.length) { + console.log('') + console.log(chalk.yellow('To use this template, you must update following to modules:')) + console.log() + + for (let i = 0; i < warnings.length; i++) { + const warning = warnings[i] + console.log(' ' + warning) + } + + console.log() + process.exit(1) + } +} diff --git a/web/build/logo.png b/web/build/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d2503fc2a44b5053b0837ebea6e87a2d339a43 GIT binary patch literal 6849 zcmaKRcUV(fvo}bjDT-7nLI_nlK}sT_69H+`qzVWDA|yaU?}j417wLi^B1KB1SLsC& zL0ag7$U(XW5YR7p&Ux?sP$d4lvMt8C^+TcQu4F zQqv!UF!I+kw)c0jhd6+g6oCr9P?7)?!qX1ui*iL{p}sKCAGuJ{{W)0z1pLF|=>h}& zt(2Lr0Z`2ig8<5i%Zk}cO5Fm=LByqGWaS`oqChZdEFmc`0hSb#gg|Aap^{+WKOYcj zHjINK)KDG%&s?Mt4CL(T=?;~U@bU2x_mLKN!#GJuK_CzbNw5SMEJorG!}_5;?R>@1 zSl)jns3WlU7^J%=(hUtfmuUCU&C3%8B5C^f5>W2Cy8jW3#{Od{lF1}|?c61##3dzA zsPlFG;l_FzBK}8>|H_Ru_H#!_7$UH4UKo3lKOA}g1(R&|e@}GINYVzX?q=_WLZCgh z)L|eJMce`D0EIwgRaNETDsr+?vQknSGAi=7H00r`QnI%oQnFxm`G2umXso9l+8*&Q z7WqF|$p49js$mdzo^BXpH#gURy=UO;=IMrYc5?@+sR4y_?d*~0^YP7d+y0{}0)zBM zIKVM(DBvICK#~7N0a+PY6)7;u=dutmNqK3AlsrUU9U`d;msiucB_|8|2kY=(7XA;G zwDA8AR)VCA#JOkxm#6oHNS^YVuOU;8p$N)2{`;oF|rQ?B~K$%rHDxXs+_G zF5|-uqHZvSzq}L;5Kcy_P+x0${33}Ofb6+TX&=y;;PkEOpz%+_bCw_{<&~ zeLV|!bP%l1qxywfVr9Z9JI+++EO^x>ZuCK);=$VIG1`kxK8F2M8AdC$iOe3cj1fo(ce4l-9 z7*zKy3={MixvUk=enQE;ED~7tv%qh&3lR<0m??@w{ILF|e#QOyPkFYK!&Up7xWNtL zOW%1QMC<3o;G9_S1;NkPB6bqbCOjeztEc6TsBM<(q9((JKiH{01+Ud=uw9B@{;(JJ z-DxI2*{pMq`q1RQc;V8@gYAY44Z!%#W~M9pRxI(R?SJ7sy7em=Z5DbuDlr@*q|25V)($-f}9c#?D%dU^RS<(wz?{P zFFHtCab*!rl(~j@0(Nadvwg8q|4!}L^>d?0al6}Rrv9$0M#^&@zjbfJy_n!%mVHK4 z6pLRIQ^Uq~dnyy$`ay51Us6WaP%&O;@49m&{G3z7xV3dLtt1VTOMYl3UW~Rm{Eq4m zF?Zl_v;?7EFx1_+#WFUXxcK78IV)FO>42@cm@}2I%pVbZqQ}3;p;sDIm&knay03a^ zn$5}Q$G!@fTwD$e(x-~aWP0h+4NRz$KlnO_H2c< z(XX#lPuW_%H#Q+c&(nRyX1-IadKR-%$4FYC0fsCmL9ky3 zKpxyjd^JFR+vg2!=HWf}2Z?@Td`0EG`kU?{8zKrvtsm)|7>pPk9nu@2^z96aU2<#` z2QhvH5w&V;wER?mopu+nqu*n8p~(%QkwSs&*0eJwa zMXR05`OSFpfyRb!Y_+H@O%Y z0=K^y6B8Gcbl?SA)qMP3Z+=C(?8zL@=74R=EVnE?vY!1BQy2@q*RUgRx4yJ$k}MnL zs!?74QciNb-LcG*&o<9=DSL>1n}ZNd)w1z3-0Pd^4ED1{qd=9|!!N?xnXjM!EuylY z5=!H>&hSofh8V?Jofyd!h`xDI1fYAuV(sZwwN~{$a}MX^=+0TH*SFp$vyxmUv7C*W zv^3Gl0+eTFgBi3FVD;$nhcp)ka*4gSskYIqQ&+M}xP9yLAkWzBI^I%zR^l1e?bW_6 zIn{mo{dD=)9@V?s^fa55jh78rP*Ze<3`tRCN4*mpO$@7a^*2B*7N_|A(Ve2VB|)_o z$=#_=aBkhe(ifX}MLT()@5?OV+~7cXC3r!%{QJxriXo9I%*3q4KT4Xxzyd{ z9;_%=W%q!Vw$Z7F3lUnY+1HZ*lO;4;VR2+i4+D(m#01OYq|L_fbnT;KN<^dkkCwtd zF7n+O7KvAw8c`JUh6LmeIrk4`F3o|AagKSMK3))_5Cv~y2Bb2!Ibg9BO7Vkz?pAYX zoI=B}+$R22&IL`NCYUYjrdhwjnMx_v=-Qcx-jmtN>!Zqf|n1^SWrHy zK|MwJ?Z#^>)rfT5YSY{qjZ&`Fjd;^vv&gF-Yj6$9-Dy$<6zeP4s+78gS2|t%Z309b z0^fp~ue_}i`U9j!<|qF92_3oB09NqgAoehQ`)<)dSfKoJl_A6Ec#*Mx9Cpd-p#$Ez z={AM*r-bQs6*z$!*VA4|QE7bf@-4vb?Q+pPKLkY2{yKsw{&udv_2v8{Dbd zm~8VAv!G~s)`O3|Q6vFUV%8%+?ZSVUa(;fhPNg#vab@J*9XE4#D%)$UU-T5`fwjz! z6&gA^`OGu6aUk{l*h9eB?opVdrHK>Q@U>&JQ_2pR%}TyOXGq_6s56_`U(WoOaAb+K zXQr#6H}>a-GYs9^bGP2Y&hSP5gEtW+GVC4=wy0wQk=~%CSXj=GH6q z-T#s!BV`xZVxm{~jr_ezYRpqqIcXC=Oq`b{lu`Rt(IYr4B91hhVC?yg{ol4WUr3v9 zOAk2LG>CIECZ-WIs0$N}F#eoIUEtZudc7DPYIjzGqDLWk_A4#(LgacooD z2K4IWs@N`Bddm-{%oy}!k0^i6Yh)uJ1S*90>|bm3TOZxcV|ywHUb(+CeX-o1|LTZM zwU>dY3R&U)T(}5#Neh?-CWT~@{6Ke@sI)uSuzoah8COy)w)B)aslJmp`WUcjdia-0 zl2Y}&L~XfA`uYQboAJ1;J{XLhYjH){cObH3FDva+^8ioOQy%Z=xyjGLmWMrzfFoH; zEi3AG`_v+%)&lDJE;iJWJDI@-X9K5O)LD~j*PBe(wu+|%ar~C+LK1+-+lK=t# z+Xc+J7qp~5q=B~rD!x78)?1+KUIbYr^5rcl&tB-cTtj+e%{gpZZ4G~6r15+d|J(ky zjg@@UzMW0k9@S#W(1H{u;Nq(7llJbq;;4t$awM;l&(2s+$l!Ay9^Ge|34CVhr7|BG z?dAR83smef^frq9V(OH+a+ki#q&-7TkWfFM=5bsGbU(8mC;>QTCWL5ydz9s6k@?+V zcjiH`VI=59P-(-DWXZ~5DH>B^_H~;4$)KUhnmGo*G!Tq8^LjfUDO)lASN*=#AY_yS zqW9UX(VOCO&p@kHdUUgsBO0KhXxn1sprK5h8}+>IhX(nSXZKwlNsjk^M|RAaqmCZB zHBolOHYBas@&{PT=R+?d8pZu zUHfyucQ`(umXSW7o?HQ3H21M`ZJal+%*)SH1B1j6rxTlG3hx1IGJN^M7{$j(9V;MZ zRKybgVuxKo#XVM+?*yTy{W+XHaU5Jbt-UG33x{u(N-2wmw;zzPH&4DE103HV@ER86 z|FZEmQb|&1s5#`$4!Cm}&`^{(4V}OP$bk`}v6q6rm;P!H)W|2i^e{7lTk2W@jo_9q z*aw|U7#+g59Fv(5qI`#O-qPj#@_P>PC#I(GSp3DLv7x-dmYK=C7lPF8a)bxb=@)B1 zUZ`EqpXV2dR}B&r`uM}N(TS99ZT0UB%IN|0H%DcVO#T%L_chrgn#m6%x4KE*IMfjX zJ%4veCEqbXZ`H`F_+fELMC@wuy_ch%t*+Z+1I}wN#C+dRrf2X{1C8=yZ_%Pt6wL_~ zZ2NN-hXOT4P4n$QFO7yYHS-4wF1Xfr-meG9Pn;uK51?hfel`d38k{W)F*|gJLT2#T z<~>spMu4(mul-8Q3*pf=N4DcI)zzjqAgbE2eOT7~&f1W3VsdD44Ffe;3mJp-V@8UC z)|qnPc12o~$X-+U@L_lWqv-RtvB~%hLF($%Ew5w>^NR82qC_0FB z)=hP1-OEx?lLi#jnLzH}a;Nvr@JDO-zQWd}#k^an$Kwml;MrD&)sC5b`s0ZkVyPkb zt}-jOq^%_9>YZe7Y}PhW{a)c39G`kg(P4@kxjcYfgB4XOOcmezdUI7j-!gs7oAo2o zx(Ph{G+YZ`a%~kzK!HTAA5NXE-7vOFRr5oqY$rH>WI6SFvWmahFav!CfRMM3%8J&c z*p+%|-fNS_@QrFr(at!JY9jCg9F-%5{nb5Bo~z@Y9m&SHYV`49GAJjA5h~h4(G!Se zZmK{Bo7ivCfvl}@A-ptkFGcWXAzj3xfl{evi-OG(TaCn1FAHxRc{}B|x+Ua1D=I6M z!C^ZIvK6aS_c&(=OQDZfm>O`Nxsw{ta&yiYPA~@e#c%N>>#rq)k6Aru-qD4(D^v)y z*>Rs;YUbD1S8^D(ps6Jbj0K3wJw>L4m)0e(6Pee3Y?gy9i0^bZO?$*sv+xKV?WBlh zAp*;v6w!a8;A7sLB*g-^<$Z4L7|5jXxxP1}hQZ<55f9<^KJ>^mKlWSGaLcO0=$jem zWyZkRwe~u{{tU63DlCaS9$Y4CP4f?+wwa(&1ou)b>72ydrFvm`Rj-0`kBJgK@nd(*Eh!(NC{F-@=FnF&Y!q`7){YsLLHf0_B6aHc# z>WIuHTyJwIH{BJ4)2RtEauC7Yq7Cytc|S)4^*t8Va3HR zg=~sN^tp9re@w=GTx$;zOWMjcg-7X3Wk^N$n;&Kf1RgVG2}2L-(0o)54C509C&77i zrjSi{X*WV=%C17((N^6R4Ya*4#6s_L99RtQ>m(%#nQ#wrRC8Y%yxkH;d!MdY+Tw@r zjpSnK`;C-U{ATcgaxoEpP0Gf+tx);buOMlK=01D|J+ROu37qc*rD(w`#O=3*O*w9?biwNoq3WN1`&Wp8TvKj3C z3HR9ssH7a&Vr<6waJrU zdLg!ieYz%U^bmpn%;(V%%ugMk92&?_XX1K@mwnVSE6!&%P%Wdi7_h`CpScvspMx?N zQUR>oadnG17#hNc$pkTp+9lW+MBKHRZ~74XWUryd)4yd zj98$%XmIL4(9OnoeO5Fnyn&fpQ9b0h4e6EHHw*l68j;>(ya`g^S&y2{O8U>1*>4zR zq*WSI_2o$CHQ?x0!wl9bpx|Cm2+kFMR)oMud1%n2=qn5nE&t@Fgr#=Zv2?}wtEz^T z9rrj=?IH*qI5{G@Rn&}^Z{+TW}mQeb9=8b<_a`&Cm#n%n~ zU47MvCBsdXFB1+adOO)03+nczfWa#vwk#r{o{dF)QWya9v2nv43Zp3%Ps}($lA02*_g25t;|T{A5snSY?3A zrRQ~(Ygh_ebltHo1VCbJb*eOAr;4cnlXLvI>*$-#AVsGg6B1r7@;g^L zFlJ_th0vxO7;-opU@WAFe;<}?!2q?RBrFK5U{*ai@NLKZ^};Ul}beukveh?TQn;$%9=R+DX07m82gP$=}Uo_%&ngV`}Hyv8g{u z3SWzTGV|cwQuFIs7ZDOqO_fGf8Q`8MwL}eUp>q?4eqCmOTcwQuXtQckPy|4F1on8l zP*h>d+cH#XQf|+6c|S{7SF(Lg>bR~l(0uY?O{OEVlaxa5@e%T&xju=o1`=OD#qc16 zSvyH*my(dcp6~VqR;o(#@m44Lug@~_qw+HA=mS#Z^4reBy8iV?H~I;{LQWk3aKK8$bLRyt$g?- { + const notifier = require('node-notifier') + + return (severity, errors) => { + if (severity !== 'error') return + + const error = errors[0] + const filename = error.file && error.file.split('!').pop() + + notifier.notify({ + title: packageConfig.name, + message: severity + ': ' + error.name, + subtitle: filename || '', + icon: path.join(__dirname, 'logo.png') + }) + } +} diff --git a/web/build/vue-loader.conf.js b/web/build/vue-loader.conf.js new file mode 100644 index 0000000..33ed58b --- /dev/null +++ b/web/build/vue-loader.conf.js @@ -0,0 +1,22 @@ +'use strict' +const utils = require('./utils') +const config = require('../config') +const isProduction = process.env.NODE_ENV === 'production' +const sourceMapEnabled = isProduction + ? config.build.productionSourceMap + : config.dev.cssSourceMap + +module.exports = { + loaders: utils.cssLoaders({ + sourceMap: sourceMapEnabled, + extract: isProduction + }), + cssSourceMap: sourceMapEnabled, + cacheBusting: config.dev.cacheBusting, + transformToRequire: { + video: ['src', 'poster'], + source: 'src', + img: 'src', + image: 'xlink:href' + } +} diff --git a/web/build/webpack.base.conf.js b/web/build/webpack.base.conf.js new file mode 100644 index 0000000..6973404 --- /dev/null +++ b/web/build/webpack.base.conf.js @@ -0,0 +1,96 @@ +'use strict' +const path = require('path') +const utils = require('./utils') +const config = require('../config') +const vueLoaderConfig = require('./vue-loader.conf') + +function resolve (dir) { + return path.join(__dirname, '..', dir) +} + +const createLintingRule = () => ({ + test: /\.(js|vue)$/, + loader: 'eslint-loader', + enforce: 'pre', + include: [resolve('src'), resolve('test')], + options: { + formatter: require('eslint-friendly-formatter'), + emitWarning: !config.dev.showEslintErrorsInOverlay + } +}) + +module.exports = { + context: path.resolve(__dirname, '../'), + entry: { + app: './src/main.js' + }, + output: { + path: config.build.assetsRoot, + filename: '[name].js', + publicPath: process.env.NODE_ENV === 'production' + ? config.build.assetsPublicPath + : config.dev.assetsPublicPath + }, + resolve: { + extensions: ['.js', '.vue', '.json'], + alias: { + 'vue$': 'vue/dist/vue.esm.js', + '@': resolve('src'), + } + }, + module: { + rules: [ + ...(config.dev.useEslint ? [createLintingRule()] : []), + { + test: /\.sass$/, + loaders: ['style', 'css', 'sass'] + }, + { + test: /\.vue$/, + loader: 'vue-loader', + options: vueLoaderConfig + }, + { + test: /\.js$/, + loader: 'babel-loader', + include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] + }, + { + test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('img/[name].[hash:7].[ext]') + } + }, + { + test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('media/[name].[hash:7].[ext]') + } + }, + { + test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('fonts/[name].[hash:7].[ext]') + } + } + ] + }, + node: { + // prevent webpack from injecting useless setImmediate polyfill because Vue + // source contains it (although only uses it if it's native). + setImmediate: false, + // prevent webpack from injecting mocks to Node native modules + // that does not make sense for the client + dgram: 'empty', + fs: 'empty', + net: 'empty', + tls: 'empty', + child_process: 'empty' + } +} diff --git a/web/build/webpack.dev.conf.js b/web/build/webpack.dev.conf.js new file mode 100644 index 0000000..070ae22 --- /dev/null +++ b/web/build/webpack.dev.conf.js @@ -0,0 +1,95 @@ +'use strict' +const utils = require('./utils') +const webpack = require('webpack') +const config = require('../config') +const merge = require('webpack-merge') +const path = require('path') +const baseWebpackConfig = require('./webpack.base.conf') +const CopyWebpackPlugin = require('copy-webpack-plugin') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') +const portfinder = require('portfinder') + +const HOST = process.env.HOST +const PORT = process.env.PORT && Number(process.env.PORT) + +const devWebpackConfig = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) + }, + // cheap-module-eval-source-map is faster for development + devtool: config.dev.devtool, + + // these devServer options should be customized in /config/index.js + devServer: { + clientLogLevel: 'warning', + historyApiFallback: { + rewrites: [ + { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, + ], + }, + hot: true, + contentBase: false, // since we use CopyWebpackPlugin. + compress: true, + host: HOST || config.dev.host, + port: PORT || config.dev.port, + open: config.dev.autoOpenBrowser, + overlay: config.dev.errorOverlay + ? { warnings: false, errors: true } + : false, + publicPath: config.dev.assetsPublicPath, + proxy: config.dev.proxyTable, + quiet: true, // necessary for FriendlyErrorsPlugin + watchOptions: { + poll: config.dev.poll, + } + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env': require('../config/dev.env') + }), + new webpack.HotModuleReplacementPlugin(), + new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. + new webpack.NoEmitOnErrorsPlugin(), + // https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', + template: 'index.html', + inject: true + }), + // copy custom static assets + new CopyWebpackPlugin([ + { + from: path.resolve(__dirname, '../static'), + to: config.dev.assetsSubDirectory, + ignore: ['.*'] + } + ]) + ] +}) + +module.exports = new Promise((resolve, reject) => { + portfinder.basePort = process.env.PORT || config.dev.port + portfinder.getPort((err, port) => { + if (err) { + reject(err) + } else { + // publish the new Port, necessary for e2e tests + process.env.PORT = port + // add port to devServer config + devWebpackConfig.devServer.port = port + + // Add FriendlyErrorsPlugin + devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ + compilationSuccessInfo: { + messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], + }, + onErrors: config.dev.notifyOnErrors + ? utils.createNotifierCallback() + : undefined + })) + + resolve(devWebpackConfig) + } + }) +}) diff --git a/web/build/webpack.prod.conf.js b/web/build/webpack.prod.conf.js new file mode 100644 index 0000000..d9f99f6 --- /dev/null +++ b/web/build/webpack.prod.conf.js @@ -0,0 +1,145 @@ +'use strict' +const path = require('path') +const utils = require('./utils') +const webpack = require('webpack') +const config = require('../config') +const merge = require('webpack-merge') +const baseWebpackConfig = require('./webpack.base.conf') +const CopyWebpackPlugin = require('copy-webpack-plugin') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const ExtractTextPlugin = require('extract-text-webpack-plugin') +const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') +const UglifyJsPlugin = require('uglifyjs-webpack-plugin') + +const env = require('../config/prod.env') + +const webpackConfig = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ + sourceMap: config.build.productionSourceMap, + extract: true, + usePostCSS: true + }) + }, + devtool: config.build.productionSourceMap ? config.build.devtool : false, + output: { + path: config.build.assetsRoot, + filename: utils.assetsPath('js/[name].[chunkhash].js'), + chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') + }, + plugins: [ + // http://vuejs.github.io/vue-loader/en/workflow/production.html + new webpack.DefinePlugin({ + 'process.env': env + }), + new UglifyJsPlugin({ + uglifyOptions: { + compress: { + warnings: false + } + }, + sourceMap: config.build.productionSourceMap, + parallel: true + }), + // extract css into its own file + new ExtractTextPlugin({ + filename: utils.assetsPath('css/[name].[contenthash].css'), + // Setting the following option to `false` will not extract CSS from codesplit chunks. + // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. + // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, + // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 + allChunks: true, + }), + // Compress extracted CSS. We are using this plugin so that possible + // duplicated CSS from different components can be deduped. + new OptimizeCSSPlugin({ + cssProcessorOptions: config.build.productionSourceMap + ? { safe: true, map: { inline: false } } + : { safe: true } + }), + // generate dist index.html with correct asset hash for caching. + // you can customize output by editing /index.html + // see https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: config.build.index, + template: 'index.html', + inject: true, + minify: { + removeComments: true, + collapseWhitespace: true, + removeAttributeQuotes: true + // more options: + // https://github.com/kangax/html-minifier#options-quick-reference + }, + // necessary to consistently work with multiple chunks via CommonsChunkPlugin + chunksSortMode: 'dependency' + }), + // keep module.id stable when vendor modules does not change + new webpack.HashedModuleIdsPlugin(), + // enable scope hoisting + new webpack.optimize.ModuleConcatenationPlugin(), + // split vendor js into its own file + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks (module) { + // any required modules inside node_modules are extracted to vendor + return ( + module.resource && + /\.js$/.test(module.resource) && + module.resource.indexOf( + path.join(__dirname, '../node_modules') + ) === 0 + ) + } + }), + // extract webpack runtime and module manifest to its own file in order to + // prevent vendor hash from being updated whenever app bundle is updated + new webpack.optimize.CommonsChunkPlugin({ + name: 'manifest', + minChunks: Infinity + }), + // This instance extracts shared chunks from code splitted chunks and bundles them + // in a separate chunk, similar to the vendor chunk + // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk + new webpack.optimize.CommonsChunkPlugin({ + name: 'app', + async: 'vendor-async', + children: true, + minChunks: 3 + }), + + // copy custom static assets + new CopyWebpackPlugin([ + { + from: path.resolve(__dirname, '../static'), + to: config.build.assetsSubDirectory, + ignore: ['.*'] + } + ]) + ] +}) + +if (config.build.productionGzip) { + const CompressionWebpackPlugin = require('compression-webpack-plugin') + + webpackConfig.plugins.push( + new CompressionWebpackPlugin({ + asset: '[path].gz[query]', + algorithm: 'gzip', + test: new RegExp( + '\\.(' + + config.build.productionGzipExtensions.join('|') + + ')$' + ), + threshold: 10240, + minRatio: 0.8 + }) + ) +} + +if (config.build.bundleAnalyzerReport) { + const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin + webpackConfig.plugins.push(new BundleAnalyzerPlugin()) +} + +module.exports = webpackConfig diff --git a/web/config/dev.env.js b/web/config/dev.env.js new file mode 100644 index 0000000..1e22973 --- /dev/null +++ b/web/config/dev.env.js @@ -0,0 +1,7 @@ +'use strict' +const merge = require('webpack-merge') +const prodEnv = require('./prod.env') + +module.exports = merge(prodEnv, { + NODE_ENV: '"development"' +}) diff --git a/web/config/index.js b/web/config/index.js new file mode 100644 index 0000000..26e6715 --- /dev/null +++ b/web/config/index.js @@ -0,0 +1,76 @@ +'use strict' +// Template version: 1.3.1 +// see http://vuejs-templates.github.io/webpack for documentation. + +const path = require('path') + +module.exports = { + dev: { + + // Paths + assetsSubDirectory: 'static', + assetsPublicPath: '/', + proxyTable: {}, + + // Various Dev Server settings + host: 'localhost', // can be overwritten by process.env.HOST + port: 8888, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined + autoOpenBrowser: true, + errorOverlay: true, + notifyOnErrors: true, + poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- + + // Use Eslint Loader? + // If true, your code will be linted during bundling and + // linting errors and warnings will be shown in the console. + useEslint: true, + // If true, eslint errors and warnings will also be shown in the error overlay + // in the browser. + showEslintErrorsInOverlay: false, + + /** + * Source Maps + */ + + // https://webpack.js.org/configuration/devtool/#development + devtool: 'cheap-module-eval-source-map', + + // If you have problems debugging vue-files in devtools, + // set this to false - it *may* help + // https://vue-loader.vuejs.org/en/options.html#cachebusting + cacheBusting: true, + + cssSourceMap: true + }, + + build: { + // Template for index.html + index: path.resolve(__dirname, '../dist/index.html'), + + // Paths + assetsRoot: path.resolve(__dirname, '../dist'), + assetsSubDirectory: 'static', + assetsPublicPath: '/', + + /** + * Source Maps + */ + + productionSourceMap: true, + // https://webpack.js.org/configuration/devtool/#production + devtool: '#source-map', + + // Gzip off by default as many popular static hosts such as + // Surge or Netlify already gzip all static assets for you. + // Before setting to `true`, make sure to: + // npm install --save-dev compression-webpack-plugin + productionGzip: false, + productionGzipExtensions: ['js', 'css'], + + // Run the build command with an extra argument to + // View the bundle analyzer report after build finishes: + // `npm run build --report` + // Set to `true` or `false` to always turn it on or off + bundleAnalyzerReport: process.env.npm_config_report + } +} diff --git a/web/config/prod.env.js b/web/config/prod.env.js new file mode 100644 index 0000000..a6f9976 --- /dev/null +++ b/web/config/prod.env.js @@ -0,0 +1,4 @@ +'use strict' +module.exports = { + NODE_ENV: '"production"' +} diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..4d4fd11 --- /dev/null +++ b/web/index.html @@ -0,0 +1,12 @@ + + + + + + 龙兵科技 + + +
    + + + diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..736063f --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,12534 @@ +{ + "name": "longbing_back", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npm.taobao.org/@babel/code-frame/download/@babel/code-frame-7.0.0-beta.44.tgz", + "integrity": "sha1-KgJkM2jegJFhYr5whlyXd08629k=", + "dev": true, + "requires": { + "@babel/highlight": "7.0.0-beta.44" + } + }, + "@babel/generator": { + "version": "7.0.0-beta.44", + "resolved": "https://registry.npm.taobao.org/@babel/generator/download/@babel/generator-7.0.0-beta.44.tgz", + "integrity": "sha1-x+Z7m1KEr89pswm1DX038+UDPUI=", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.44", + "jsesc": "^2.5.1", + "lodash": "^4.2.0", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "2.5.2", + "resolved": "http://registry.npm.taobao.org/jsesc/download/jsesc-2.5.2.tgz", + "integrity": "sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npm.taobao.org/@babel/helper-function-name/download/@babel/helper-function-name-7.0.0-beta.44.tgz", + "integrity": "sha1-4YVSqq4iMRAKbkheA4VLw1MtRN0=", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "7.0.0-beta.44", + "@babel/template": "7.0.0-beta.44", + "@babel/types": "7.0.0-beta.44" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npm.taobao.org/@babel/helper-get-function-arity/download/@babel/helper-get-function-arity-7.0.0-beta.44.tgz", + "integrity": "sha1-0Dym3SufewseazLFbHKDYUDbOhU=", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.44" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0-beta.44", + "resolved": "https://registry.npm.taobao.org/@babel/helper-split-export-declaration/download/@babel/helper-split-export-declaration-7.0.0-beta.44.tgz", + "integrity": "sha1-wLNRc14PvLOCLIrY205YOwXr2dw=", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.44" + } + }, + "@babel/highlight": { + "version": "7.0.0-beta.44", + "resolved": "https://registry.npm.taobao.org/@babel/highlight/download/@babel/highlight-7.0.0-beta.44.tgz?cache=0&sync_timestamp=1562245140883&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhighlight%2Fdownload%2F%40babel%2Fhighlight-7.0.0-beta.44.tgz", + "integrity": "sha1-GMlM5UORaoBVPtzc9oGJCyAHR9U=", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^3.0.0" + } + }, + "@babel/template": { + "version": "7.0.0-beta.44", + "resolved": "https://registry.npm.taobao.org/@babel/template/download/@babel/template-7.0.0-beta.44.tgz", + "integrity": "sha1-+IMvT9zuXVm/UV5ZX8UQbFKbOU8=", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0-beta.44", + "@babel/types": "7.0.0-beta.44", + "babylon": "7.0.0-beta.44", + "lodash": "^4.2.0" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npm.taobao.org/babylon/download/babylon-7.0.0-beta.44.tgz", + "integrity": "sha1-iRWeFebjDFCW4i1zjYwK+KDoyh0=", + "dev": true + } + } + }, + "@babel/traverse": { + "version": "7.0.0-beta.44", + "resolved": "https://registry.npm.taobao.org/@babel/traverse/download/@babel/traverse-7.0.0-beta.44.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Ftraverse%2Fdownload%2F%40babel%2Ftraverse-7.0.0-beta.44.tgz", + "integrity": "sha1-qXCixFR3rRgBfi5GWgYG/u4NKWY=", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0-beta.44", + "@babel/generator": "7.0.0-beta.44", + "@babel/helper-function-name": "7.0.0-beta.44", + "@babel/helper-split-export-declaration": "7.0.0-beta.44", + "@babel/types": "7.0.0-beta.44", + "babylon": "7.0.0-beta.44", + "debug": "^3.1.0", + "globals": "^11.1.0", + "invariant": "^2.2.0", + "lodash": "^4.2.0" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npm.taobao.org/babylon/download/babylon-7.0.0-beta.44.tgz", + "integrity": "sha1-iRWeFebjDFCW4i1zjYwK+KDoyh0=", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "http://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npm.taobao.org/globals/download/globals-11.12.0.tgz", + "integrity": "sha1-q4eVM4hooLq9hSV1gBjCp+uVxC4=", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.0.0-beta.44", + "resolved": "https://registry.npm.taobao.org/@babel/types/download/@babel/types-7.0.0-beta.44.tgz", + "integrity": "sha1-axsWRZH3fewKA0KsqZXy0Eazp1c=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.2.0", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/to-fast-properties/download/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } + } + }, + "@types/q": { + "version": "1.5.2", + "resolved": "https://registry.npm.taobao.org/@types/q/download/@types/q-1.5.2.tgz", + "integrity": "sha1-aQoUdbhPKohP0HzXl8APXzE1bqg=", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz", + "integrity": "sha1-UxvHJlF6OytB+FACHGzBXqq1B80=", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npm.taobao.org/acorn/download/acorn-5.7.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Facorn%2Fdownload%2Facorn-5.7.3.tgz", + "integrity": "sha1-Z6ojG/iBKXS4UjWpZ3Hra9B+onk=", + "dev": true + }, + "acorn-dynamic-import": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/acorn-dynamic-import/download/acorn-dynamic-import-2.0.2.tgz", + "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", + "dev": true, + "requires": { + "acorn": "^4.0.3" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npm.taobao.org/acorn/download/acorn-4.0.13.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Facorn%2Fdownload%2Facorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + } + } + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "http://registry.npm.taobao.org/acorn-jsx/download/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npm.taobao.org/acorn/download/acorn-3.3.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Facorn%2Fdownload%2Facorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-5.5.2.tgz?cache=0&sync_timestamp=1563141444360&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npm.taobao.org/ajv-keywords/download/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "http://registry.npm.taobao.org/align-text/download/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/alphanum-sort/download/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "dev": true + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npm.taobao.org/ansi-escapes/download/ansi-escapes-3.2.0.tgz?cache=0&sync_timestamp=1560270692491&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-escapes%2Fdownload%2Fansi-escapes-3.2.0.tgz", + "integrity": "sha1-h4C5j/nb9WOBUtHx/lwde0RCl2s=", + "dev": true + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "http://registry.npm.taobao.org/ansi-html/download/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-3.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-3.2.1.tgz", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/anymatch/download/anymatch-2.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fanymatch%2Fdownload%2Fanymatch-2.0.0.tgz", + "integrity": "sha1-vLJLTzeTTZqnrBe0ra+J58du8us=", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/normalize-path/download/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/aproba/download/aproba-1.2.0.tgz", + "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "http://registry.npm.taobao.org/argparse/download/argparse-1.0.10.tgz", + "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "http://registry.npm.taobao.org/arr-diff/download/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/arr-flatten/download/arr-flatten-1.1.0.tgz", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "http://registry.npm.taobao.org/arr-union/download/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/array-find-index/download/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "array-includes": { + "version": "3.0.3", + "resolved": "http://registry.npm.taobao.org/array-includes/download/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/array-union/download/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "http://registry.npm.taobao.org/array-uniq/download/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "http://registry.npm.taobao.org/array-unique/download/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npm.taobao.org/asn1.js/download/asn1.js-4.10.1.tgz", + "integrity": "sha1-ucK/WAXx5kqt7tbfOiv6+1pz9aA=", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npm.taobao.org/assert/download/assert-1.5.0.tgz", + "integrity": "sha1-VcEJqvbgrv2z3EtxJAxwv1dLGOs=", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.1.tgz?cache=0&sync_timestamp=1560975547815&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npm.taobao.org/util/download/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/assign-symbols/download/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npm.taobao.org/async/download/async-2.6.3.tgz?cache=0&sync_timestamp=1563147078542&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fasync%2Fdownload%2Fasync-2.6.3.tgz", + "integrity": "sha1-1yYl4jRKNlbjo61Pp0n6gymdgv8=", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npm.taobao.org/async-each/download/async-each-1.0.3.tgz", + "integrity": "sha1-tyfb+H12UWAvBvTUrDh/R9kbDL8=", + "dev": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/async-limiter/download/async-limiter-1.0.0.tgz", + "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "http://registry.npm.taobao.org/atob/download/atob-2.1.2.tgz", + "integrity": "sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=", + "dev": true + }, + "autoprefixer": { + "version": "7.2.6", + "resolved": "https://registry.npm.taobao.org/autoprefixer/download/autoprefixer-7.2.6.tgz", + "integrity": "sha1-JWZy+G98c12oScTwfQCKuwVgZ9w=", + "dev": true, + "requires": { + "browserslist": "^2.11.3", + "caniuse-lite": "^1.0.30000805", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^6.0.17", + "postcss-value-parser": "^3.2.3" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "http://registry.npm.taobao.org/babel-code-frame/download/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "http://registry.npm.taobao.org/babel-core/download/babel-core-6.26.3.tgz", + "integrity": "sha1-suLwnjQtDwyI4vAuBneUEl51wgc=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-eslint": { + "version": "8.2.6", + "resolved": "https://registry.npm.taobao.org/babel-eslint/download/babel-eslint-8.2.6.tgz", + "integrity": "sha1-YnDQxzIFYoBnwPeuFpOp55es79k=", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0-beta.44", + "@babel/traverse": "7.0.0-beta.44", + "@babel/types": "7.0.0-beta.44", + "babylon": "7.0.0-beta.44", + "eslint-scope": "3.7.1", + "eslint-visitor-keys": "^1.0.0" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.44", + "resolved": "http://registry.npm.taobao.org/babylon/download/babylon-7.0.0-beta.44.tgz", + "integrity": "sha1-iRWeFebjDFCW4i1zjYwK+KDoyh0=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "http://registry.npm.taobao.org/babel-generator/download/babel-generator-6.26.1.tgz", + "integrity": "sha1-GERAjTuPDTWkBOp6wYDwh6YBvZA=", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-helper-bindify-decorators": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-helper-bindify-decorators/download/babel-helper-bindify-decorators-6.24.1.tgz", + "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-helper-builder-binary-assignment-operator-visitor/download/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "dev": true, + "requires": { + "babel-helper-explode-assignable-expression": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-helper-call-delegate/download/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-define-map": { + "version": "6.26.0", + "resolved": "http://registry.npm.taobao.org/babel-helper-define-map/download/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-helper-explode-assignable-expression/download/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-explode-class": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-helper-explode-class/download/babel-helper-explode-class-6.24.1.tgz", + "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", + "dev": true, + "requires": { + "babel-helper-bindify-decorators": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-helper-function-name/download/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "dev": true, + "requires": { + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-helper-get-function-arity/download/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-helper-hoist-variables/download/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-helper-optimise-call-expression/download/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-regex": { + "version": "6.26.0", + "resolved": "http://registry.npm.taobao.org/babel-helper-regex/download/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-helper-remap-async-to-generator/download/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-helper-replace-supers/download/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "dev": true, + "requires": { + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-vue-jsx-merge-props": { + "version": "2.0.3", + "resolved": "http://registry.npm.taobao.org/babel-helper-vue-jsx-merge-props/download/babel-helper-vue-jsx-merge-props-2.0.3.tgz", + "integrity": "sha1-Iq69OzOQIyjlEyk6jkmSs4T58bY=", + "dev": true + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-helpers/download/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-loader": { + "version": "7.1.5", + "resolved": "https://registry.npm.taobao.org/babel-loader/download/babel-loader-7.1.5.tgz", + "integrity": "sha1-4+4M1zlKpVfgE7AtPkkr/QeqbWg=", + "dev": true, + "requires": { + "find-cache-dir": "^1.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "http://registry.npm.taobao.org/babel-messages/download/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-check-es2015-constants/download/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-syntax-async-functions/download/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "babel-plugin-syntax-async-generators": { + "version": "6.13.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-syntax-async-generators/download/babel-plugin-syntax-async-generators-6.13.0.tgz", + "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", + "dev": true + }, + "babel-plugin-syntax-class-properties": { + "version": "6.13.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-syntax-class-properties/download/babel-plugin-syntax-class-properties-6.13.0.tgz", + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", + "dev": true + }, + "babel-plugin-syntax-decorators": { + "version": "6.13.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-syntax-decorators/download/babel-plugin-syntax-decorators-6.13.0.tgz", + "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", + "dev": true + }, + "babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-syntax-dynamic-import/download/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", + "dev": true + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-syntax-exponentiation-operator/download/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "dev": true + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-syntax-jsx/download/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", + "dev": true + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-syntax-object-rest-spread/download/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", + "dev": true + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-syntax-trailing-function-commas/download/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "babel-plugin-transform-async-generator-functions": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-async-generator-functions/download/babel-plugin-transform-async-generator-functions-6.24.1.tgz", + "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-generators": "^6.5.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-async-to-generator/download/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-functions": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-class-properties": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-class-properties/download/babel-plugin-transform-class-properties-6.24.1.tgz", + "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-plugin-syntax-class-properties": "^6.8.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-decorators": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-decorators/download/babel-plugin-transform-decorators-6.24.1.tgz", + "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", + "dev": true, + "requires": { + "babel-helper-explode-class": "^6.24.1", + "babel-plugin-syntax-decorators": "^6.13.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-arrow-functions/download/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-block-scoped-functions/download/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-block-scoping/download/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-classes/download/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "dev": true, + "requires": { + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-computed-properties/download/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-destructuring/download/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-duplicate-keys/download/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-for-of/download/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-function-name/download/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-literals/download/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-modules-amd/download/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-modules-commonjs/download/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha1-WKeThjqefKhwvcWogRF/+sJ9tvM=", + "dev": true, + "requires": { + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + } + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-modules-systemjs/download/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-modules-umd/download/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-object-super/download/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true, + "requires": { + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-parameters/download/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "dev": true, + "requires": { + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-shorthand-properties/download/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-spread/download/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-sticky-regex/download/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-template-literals/download/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-typeof-symbol/download/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-es2015-unicode-regex/download/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + } + }, + "babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-exponentiation-operator/download/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true, + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", + "babel-plugin-syntax-exponentiation-operator": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-object-rest-spread": { + "version": "6.26.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-object-rest-spread/download/babel-plugin-transform-object-rest-spread-6.26.0.tgz", + "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", + "dev": true, + "requires": { + "babel-plugin-syntax-object-rest-spread": "^6.8.0", + "babel-runtime": "^6.26.0" + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-regenerator/download/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "dev": true, + "requires": { + "regenerator-transform": "^0.10.0" + } + }, + "babel-plugin-transform-runtime": { + "version": "6.23.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-runtime/download/babel-plugin-transform-runtime-6.23.0.tgz", + "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-strict-mode/download/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-vue-jsx": { + "version": "3.7.0", + "resolved": "http://registry.npm.taobao.org/babel-plugin-transform-vue-jsx/download/babel-plugin-transform-vue-jsx-3.7.0.tgz", + "integrity": "sha1-1ASS5mkqNrWU9+mhko9D6Wl0CWA=", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "babel-preset-env": { + "version": "1.7.0", + "resolved": "http://registry.npm.taobao.org/babel-preset-env/download/babel-preset-env-1.7.0.tgz", + "integrity": "sha1-3qefpOvriDzTXasH4mDBycBN93o=", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-to-generator": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.23.0", + "babel-plugin-transform-es2015-classes": "^6.23.0", + "babel-plugin-transform-es2015-computed-properties": "^6.22.0", + "babel-plugin-transform-es2015-destructuring": "^6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0", + "babel-plugin-transform-es2015-for-of": "^6.23.0", + "babel-plugin-transform-es2015-function-name": "^6.22.0", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-umd": "^6.23.0", + "babel-plugin-transform-es2015-object-super": "^6.22.0", + "babel-plugin-transform-es2015-parameters": "^6.23.0", + "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.22.0", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", + "babel-plugin-transform-exponentiation-operator": "^6.22.0", + "babel-plugin-transform-regenerator": "^6.22.0", + "browserslist": "^3.2.6", + "invariant": "^2.2.2", + "semver": "^5.3.0" + }, + "dependencies": { + "browserslist": { + "version": "3.2.8", + "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-3.2.8.tgz", + "integrity": "sha1-sABTYdZHHw9ZUnl6dvyYXx+Xj8Y=", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" + } + } + } + }, + "babel-preset-stage-2": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-preset-stage-2/download/babel-preset-stage-2-6.24.1.tgz", + "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", + "dev": true, + "requires": { + "babel-plugin-syntax-dynamic-import": "^6.18.0", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-decorators": "^6.24.1", + "babel-preset-stage-3": "^6.24.1" + } + }, + "babel-preset-stage-3": { + "version": "6.24.1", + "resolved": "http://registry.npm.taobao.org/babel-preset-stage-3/download/babel-preset-stage-3-6.24.1.tgz", + "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", + "dev": true, + "requires": { + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-generator-functions": "^6.24.1", + "babel-plugin-transform-async-to-generator": "^6.24.1", + "babel-plugin-transform-exponentiation-operator": "^6.24.1", + "babel-plugin-transform-object-rest-spread": "^6.22.0" + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "http://registry.npm.taobao.org/babel-register/download/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "http://registry.npm.taobao.org/babel-runtime/download/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "http://registry.npm.taobao.org/babel-template/download/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "http://registry.npm.taobao.org/babel-traverse/download/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "http://registry.npm.taobao.org/babel-types/download/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "http://registry.npm.taobao.org/babylon/download/babylon-6.18.0.tgz", + "integrity": "sha1-ry87iPpvXB5MY00aD46sT1WzleM=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "http://registry.npm.taobao.org/base/download/base-0.11.2.tgz", + "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-6.0.2.tgz", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "dev": true + } + } + }, + "base64-js": { + "version": "1.3.0", + "resolved": "http://registry.npm.taobao.org/base64-js/download/base64-js-1.3.0.tgz", + "integrity": "sha1-yrHmEY8FEJXli1KBrqjBzSK/wOM=", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "http://registry.npm.taobao.org/batch/download/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bfj-node4": { + "version": "5.3.1", + "resolved": "http://registry.npm.taobao.org/bfj-node4/download/bfj-node4-5.3.1.tgz", + "integrity": "sha1-4j2LJwV/HQIU/FYRQq2duZjyaDA=", + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "check-types": "^7.3.0", + "tryer": "^1.0.0" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "http://registry.npm.taobao.org/big.js/download/big.js-5.2.2.tgz", + "integrity": "sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg=", + "dev": true + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-1.13.1.tgz", + "integrity": "sha1-WYr+VHVbKGilMw0q/51Ou1Mgm2U=", + "dev": true + }, + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npm.taobao.org/bluebird/download/bluebird-3.5.5.tgz", + "integrity": "sha1-qNCv1zJR7/u9X+OEp31zADwXpx8=", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npm.taobao.org/bn.js/download/bn.js-4.11.8.tgz", + "integrity": "sha1-LN4J617jQfSEdGuwMJsyU7GxRC8=", + "dev": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npm.taobao.org/body-parser/download/body-parser-1.19.0.tgz", + "integrity": "sha1-lrJwnlfJxOCab9Zqj9l5hE9p8Io=", + "dev": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "http://registry.npm.taobao.org/bonjour/download/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + }, + "dependencies": { + "array-flatten": { + "version": "2.1.2", + "resolved": "http://registry.npm.taobao.org/array-flatten/download/array-flatten-2.1.2.tgz", + "integrity": "sha1-JO+AoowaiTYX4hSbDG0NeIKTsJk=", + "dev": true + } + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/boolbase/download/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "http://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npm.taobao.org/braces/download/braces-2.3.2.tgz", + "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/brorand/download/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/browserify-aes/download/browserify-aes-1.2.0.tgz", + "integrity": "sha1-Mmc0ZC9APavDADIJhTu3CtQo70g=", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/browserify-cipher/download/browserify-cipher-1.0.1.tgz", + "integrity": "sha1-jWR0wbhwv9q807z8wZNKEOlPFfA=", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/browserify-des/download/browserify-des-1.0.2.tgz", + "integrity": "sha1-OvTx9Zg5QDVy8cZiBDdfen9wPpw=", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/browserify-rsa/download/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "http://registry.npm.taobao.org/browserify-sign/download/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "http://registry.npm.taobao.org/browserify-zlib/download/browserify-zlib-0.2.0.tgz", + "integrity": "sha1-KGlFnZqjviRf6P4sofRuLn9U1z8=", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "2.11.3", + "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-2.11.3.tgz", + "integrity": "sha1-/jYWeu0bvN5IJ+v+cTR6LMcLmbI=", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000792", + "electron-to-chromium": "^1.3.30" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "http://registry.npm.taobao.org/buffer/download/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz", + "integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=", + "dev": true + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/buffer-indexof/download/buffer-indexof-1.1.1.tgz", + "integrity": "sha1-Uvq8xqYG0aADAoAmSO9o9jnaJow=", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "http://registry.npm.taobao.org/buffer-xor/download/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/builtin-status-codes/download/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "bytes": { + "version": "3.1.0", + "resolved": "http://registry.npm.taobao.org/bytes/download/bytes-3.1.0.tgz", + "integrity": "sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY=", + "dev": true + }, + "cacache": { + "version": "10.0.4", + "resolved": "https://registry.npm.taobao.org/cacache/download/cacache-10.0.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcacache%2Fdownload%2Fcacache-10.0.4.tgz", + "integrity": "sha1-ZFI2eZnv+dQYiu/ZoU6dfGomNGA=", + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/cache-base/download/cache-base-1.0.1.tgz", + "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/caller-callsite/download/caller-callsite-2.0.0.tgz?cache=0&sync_timestamp=1562668966653&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcaller-callsite%2Fdownload%2Fcaller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/callsites/download/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + } + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "http://registry.npm.taobao.org/caller-path/download/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "http://registry.npm.taobao.org/callsites/download/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camel-case": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/camel-case/download/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "1.2.1", + "resolved": "http://registry.npm.taobao.org/camelcase/download/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/camelcase-keys/download/camelcase-keys-2.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcamelcase-keys%2Fdownload%2Fcamelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/camelcase/download/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, + "caniuse-api": { + "version": "1.6.1", + "resolved": "http://registry.npm.taobao.org/caniuse-api/download/caniuse-api-1.6.1.tgz", + "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", + "dev": true, + "requires": { + "browserslist": "^1.3.6", + "caniuse-db": "^1.0.30000529", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + }, + "dependencies": { + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "dev": true, + "requires": { + "caniuse-db": "^1.0.30000639", + "electron-to-chromium": "^1.2.7" + } + } + } + }, + "caniuse-db": { + "version": "1.0.30000984", + "resolved": "https://registry.npm.taobao.org/caniuse-db/download/caniuse-db-1.0.30000984.tgz?cache=0&sync_timestamp=1562995758408&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcaniuse-db%2Fdownload%2Fcaniuse-db-1.0.30000984.tgz", + "integrity": "sha1-PkpT148zQD6THvHSstsHVW0lPf8=", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30000984", + "resolved": "https://registry.npm.taobao.org/caniuse-lite/download/caniuse-lite-1.0.30000984.tgz", + "integrity": "sha1-3JbDxGnpvPxq1b3STHfskY6nb+A=", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "http://registry.npm.taobao.org/center-align/download/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-2.4.2.tgz", + "integrity": "sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ=", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npm.taobao.org/chardet/download/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "check-types": { + "version": "7.4.0", + "resolved": "https://registry.npm.taobao.org/check-types/download/check-types-7.4.0.tgz", + "integrity": "sha1-A3jsG5YW7HH3dJMaPGUW+tjBUvQ=", + "dev": true + }, + "chokidar": { + "version": "2.1.6", + "resolved": "https://registry.npm.taobao.org/chokidar/download/chokidar-2.1.6.tgz?cache=0&sync_timestamp=1562457945399&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchokidar%2Fdownload%2Fchokidar-2.1.6.tgz", + "integrity": "sha1-tsrWU6kp4kTOioNCRBZNJB+pVMU=", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "chownr": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/chownr/download/chownr-1.1.2.tgz", + "integrity": "sha1-oY8eCyacimpdPIbrKYvrFMPde/Y=", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "http://registry.npm.taobao.org/cipher-base/download/cipher-base-1.0.4.tgz", + "integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "http://registry.npm.taobao.org/circular-json/download/circular-json-0.3.3.tgz", + "integrity": "sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY=", + "dev": true + }, + "clap": { + "version": "1.2.3", + "resolved": "http://registry.npm.taobao.org/clap/download/clap-1.2.3.tgz", + "integrity": "sha1-TzZ0WzIAhJJVf0ZBLWbVDLmbzlE=", + "dev": true, + "requires": { + "chalk": "^1.1.3" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "http://registry.npm.taobao.org/class-utils/download/class-utils-0.3.6.tgz", + "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "http://registry.npm.taobao.org/clean-css/download/clean-css-4.2.1.tgz", + "integrity": "sha1-LUEe92uFabbQyEBo2r6FsKpeXBc=", + "dev": true, + "requires": { + "source-map": "~0.6.0" + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/cli-cursor/download/cli-cursor-2.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcli-cursor%2Fdownload%2Fcli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-spinners": { + "version": "1.3.1", + "resolved": "https://registry.npm.taobao.org/cli-spinners/download/cli-spinners-1.3.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcli-spinners%2Fdownload%2Fcli-spinners-1.3.1.tgz", + "integrity": "sha1-ACwZkJEtDVlYDJO9NsBW3pnkJZo=", + "dev": true + }, + "cli-width": { + "version": "2.2.0", + "resolved": "http://registry.npm.taobao.org/cli-width/download/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "http://registry.npm.taobao.org/cliui/download/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "http://registry.npm.taobao.org/wordwrap/download/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + } + } + }, + "clone": { + "version": "1.0.4", + "resolved": "http://registry.npm.taobao.org/clone/download/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "http://registry.npm.taobao.org/co/download/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "coa": { + "version": "1.0.4", + "resolved": "http://registry.npm.taobao.org/coa/download/coa-1.0.4.tgz", + "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", + "dev": true, + "requires": { + "q": "^1.1.2" + } + }, + "coalescy": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/coalescy/download/coalescy-1.0.0.tgz", + "integrity": "sha1-SwZYRrg2NhrabEtKSr9LwcrDG/E=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/code-point-at/download/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/collection-visit/download/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "0.11.4", + "resolved": "https://registry.npm.taobao.org/color/download/color-0.11.4.tgz", + "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", + "dev": true, + "requires": { + "clone": "^1.0.2", + "color-convert": "^1.3.0", + "color-string": "^0.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "http://registry.npm.taobao.org/color-convert/download/color-convert-1.9.3.tgz", + "integrity": "sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg=", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/color-name/download/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color-string": { + "version": "0.3.0", + "resolved": "http://registry.npm.taobao.org/color-string/download/color-string-0.3.0.tgz", + "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", + "dev": true, + "requires": { + "color-name": "^1.0.0" + } + }, + "colormin": { + "version": "1.1.2", + "resolved": "http://registry.npm.taobao.org/colormin/download/colormin-1.1.2.tgz", + "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", + "dev": true, + "requires": { + "color": "^0.11.0", + "css-color-names": "0.0.4", + "has": "^1.0.1" + } + }, + "colors": { + "version": "1.1.2", + "resolved": "http://registry.npm.taobao.org/colors/download/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "commander": { + "version": "2.17.1", + "resolved": "http://registry.npm.taobao.org/commander/download/commander-2.17.1.tgz", + "integrity": "sha1-vXerfebelCBc6sxy8XFtKfIKd78=", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/commondir/download/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npm.taobao.org/component-emitter/download/component-emitter-1.3.0.tgz", + "integrity": "sha1-FuQHD7qK4ptnnyIVhT7hgasuq8A=", + "dev": true + }, + "compressible": { + "version": "2.0.17", + "resolved": "https://registry.npm.taobao.org/compressible/download/compressible-2.0.17.tgz", + "integrity": "sha1-bowQihatWDhKl386SCyiC/8vOME=", + "dev": true, + "requires": { + "mime-db": ">= 1.40.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "http://registry.npm.taobao.org/compression/download/compression-1.7.4.tgz", + "integrity": "sha1-lVI+/xcMpXwpoMpB5v4TH0Hlu48=", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "http://registry.npm.taobao.org/concat-map/download/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "http://registry.npm.taobao.org/concat-stream/download/concat-stream-1.6.2.tgz", + "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "http://registry.npm.taobao.org/connect-history-api-fallback/download/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha1-izIIk1kwjRERFdgcrT/Oq4iPl7w=", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/console-browserify/download/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "consolidate": { + "version": "0.14.5", + "resolved": "http://registry.npm.taobao.org/consolidate/download/consolidate-0.14.5.tgz", + "integrity": "sha1-WiUEe8dvcwcmZ8jLUsmJiI9JTGM=", + "dev": true, + "requires": { + "bluebird": "^3.1.1" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/constants-browserify/download/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "contains-path": { + "version": "0.1.0", + "resolved": "http://registry.npm.taobao.org/contains-path/download/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "http://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.3.tgz", + "integrity": "sha1-4TDK9+cnkIfFYWwgB9BIVpiYT70=", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "http://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "http://registry.npm.taobao.org/convert-source-map/download/convert-source-map-1.6.0.tgz", + "integrity": "sha1-UbU3qMQ+DwTewZk7/83VBOdYrCA=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npm.taobao.org/cookie/download/cookie-0.4.0.tgz", + "integrity": "sha1-vrQ35wIrO21JAZ0IhmUwPr6cFLo=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "http://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "http://registry.npm.taobao.org/copy-concurrently/download/copy-concurrently-1.0.5.tgz", + "integrity": "sha1-kilzmMrjSTf8r9bsgTnBgFHwteA=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "http://registry.npm.taobao.org/copy-descriptor/download/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-webpack-plugin": { + "version": "4.6.0", + "resolved": "https://registry.npm.taobao.org/copy-webpack-plugin/download/copy-webpack-plugin-4.6.0.tgz", + "integrity": "sha1-5/QN2KaEd9QF3Rt6hUquMksVi64=", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^1.1.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "serialize-javascript": "^1.4.0" + } + }, + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npm.taobao.org/core-js/download/core-js-2.6.9.tgz", + "integrity": "sha1-a0shRiDINBUuF5Mjcn/Bl0GwhPI=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npm.taobao.org/cosmiconfig/download/cosmiconfig-5.2.1.tgz", + "integrity": "sha1-BA9yaAnFked6F8CjYmykW08Wixo=", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/esprima/download/esprima-4.0.1.tgz", + "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "http://registry.npm.taobao.org/js-yaml/download/js-yaml-3.13.1.tgz", + "integrity": "sha1-r/FRswv9+o5J4F2iLnQV6d+jeEc=", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npm.taobao.org/parse-json/download/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "http://registry.npm.taobao.org/create-ecdh/download/create-ecdh-4.0.3.tgz", + "integrity": "sha1-yREbbzMEXEaX8UR4f5JUzcd8Rf8=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/create-hash/download/create-hash-1.2.0.tgz", + "integrity": "sha1-iJB4rxGmN1a8+1m9IhmWvjqe8ZY=", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "http://registry.npm.taobao.org/create-hmac/download/create-hmac-1.1.7.tgz", + "integrity": "sha1-aRcMeLOrlXFHsriwRXLkfq0iQ/8=", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "http://registry.npm.taobao.org/cross-spawn/download/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "http://registry.npm.taobao.org/crypto-browserify/download/crypto-browserify-3.12.0.tgz", + "integrity": "sha1-OWz58xN/A+S45TLFj2mCVOAPgOw=", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "http://registry.npm.taobao.org/css-color-names/download/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", + "dev": true + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/css-declaration-sorter/download/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha1-wZiUD2OnbX42wecQGLABchBUyyI=", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz", + "integrity": "sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "css-loader": { + "version": "0.28.11", + "resolved": "https://registry.npm.taobao.org/css-loader/download/css-loader-0.28.11.tgz", + "integrity": "sha1-w/mGSnAL4nEbtaJGKyOJsaOS2rc=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "css-selector-tokenizer": "^0.7.0", + "cssnano": "^3.10.0", + "icss-utils": "^2.1.0", + "loader-utils": "^1.0.2", + "lodash.camelcase": "^4.3.0", + "object-assign": "^4.1.1", + "postcss": "^5.0.6", + "postcss-modules-extract-imports": "^1.2.0", + "postcss-modules-local-by-default": "^1.2.0", + "postcss-modules-scope": "^1.1.0", + "postcss-modules-values": "^1.3.0", + "postcss-value-parser": "^3.3.0", + "source-list-map": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/css-select/download/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "http://registry.npm.taobao.org/css-select-base-adapter/download/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha1-Oy/0lyzDYquIVhUHqVQIoUMhNdc=", + "dev": true + }, + "css-selector-tokenizer": { + "version": "0.7.1", + "resolved": "http://registry.npm.taobao.org/css-selector-tokenizer/download/css-selector-tokenizer-0.7.1.tgz", + "integrity": "sha1-oXcnGovKUBkXL0+JH8bu2cv2jV0=", + "dev": true, + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + }, + "dependencies": { + "regexpu-core": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/regexpu-core/download/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + } + } + }, + "css-tree": { + "version": "1.0.0-alpha.33", + "resolved": "https://registry.npm.taobao.org/css-tree/download/css-tree-1.0.0-alpha.33.tgz", + "integrity": "sha1-lw4g5akfejeN3Q/FjQtsjU876T4=", + "dev": true, + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.5.3" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "css-unit-converter": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/css-unit-converter/download/css-unit-converter-1.1.1.tgz", + "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=", + "dev": true + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npm.taobao.org/css-what/download/css-what-2.1.3.tgz?cache=0&sync_timestamp=1563046878296&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcss-what%2Fdownload%2Fcss-what-2.1.3.tgz", + "integrity": "sha1-ptdgRXM2X+dGhsPzEcVlE9iChfI=", + "dev": true + }, + "cssesc": { + "version": "0.1.0", + "resolved": "http://registry.npm.taobao.org/cssesc/download/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "cssnano": { + "version": "3.10.0", + "resolved": "http://registry.npm.taobao.org/cssnano/download/cssnano-3.10.0.tgz", + "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", + "dev": true, + "requires": { + "autoprefixer": "^6.3.1", + "decamelize": "^1.1.2", + "defined": "^1.0.0", + "has": "^1.0.1", + "object-assign": "^4.0.1", + "postcss": "^5.0.14", + "postcss-calc": "^5.2.0", + "postcss-colormin": "^2.1.8", + "postcss-convert-values": "^2.3.4", + "postcss-discard-comments": "^2.0.4", + "postcss-discard-duplicates": "^2.0.1", + "postcss-discard-empty": "^2.0.1", + "postcss-discard-overridden": "^0.1.1", + "postcss-discard-unused": "^2.2.1", + "postcss-filter-plugins": "^2.0.0", + "postcss-merge-idents": "^2.1.5", + "postcss-merge-longhand": "^2.0.1", + "postcss-merge-rules": "^2.0.3", + "postcss-minify-font-values": "^1.0.2", + "postcss-minify-gradients": "^1.0.1", + "postcss-minify-params": "^1.0.4", + "postcss-minify-selectors": "^2.0.4", + "postcss-normalize-charset": "^1.1.0", + "postcss-normalize-url": "^3.0.7", + "postcss-ordered-values": "^2.1.0", + "postcss-reduce-idents": "^2.2.2", + "postcss-reduce-initial": "^1.0.0", + "postcss-reduce-transforms": "^1.0.3", + "postcss-svgo": "^2.1.1", + "postcss-unique-selectors": "^2.0.2", + "postcss-value-parser": "^3.2.3", + "postcss-zindex": "^2.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "autoprefixer": { + "version": "6.7.7", + "resolved": "https://registry.npm.taobao.org/autoprefixer/download/autoprefixer-6.7.7.tgz", + "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", + "dev": true, + "requires": { + "browserslist": "^1.7.6", + "caniuse-db": "^1.0.30000634", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^5.2.16", + "postcss-value-parser": "^3.2.3" + } + }, + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "dev": true, + "requires": { + "caniuse-db": "^1.0.30000639", + "electron-to-chromium": "^1.2.7" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "http://registry.npm.taobao.org/cssnano-preset-default/download/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha1-UexmLM/KD4izltzZZ5zbkxvhf3Y=", + "dev": true, + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + }, + "dependencies": { + "browserslist": { + "version": "4.6.6", + "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-4.6.6.tgz", + "integrity": "sha1-bkv0Z83lILydvfN0fa+gNTHOxFM=", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000984", + "electron-to-chromium": "^1.3.191", + "node-releases": "^1.1.25" + } + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/caniuse-api/download/caniuse-api-3.0.0.tgz", + "integrity": "sha1-Xk2Q4idJYdRikZl99Znj7QCO5MA=", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "coa": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/coa/download/coa-2.0.2.tgz", + "integrity": "sha1-Q/bCEVG07yv1cYfbDXPeIp4+fsM=", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, + "color": { + "version": "3.1.2", + "resolved": "https://registry.npm.taobao.org/color/download/color-3.1.2.tgz", + "integrity": "sha1-aBSOf4XUGtdknF+oyBBvCY0inhA=", + "dev": true, + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-string": { + "version": "1.5.3", + "resolved": "http://registry.npm.taobao.org/color-string/download/color-string-1.5.3.tgz", + "integrity": "sha1-ybvF8BtYtUkvPWhXRZy2WQziBMw=", + "dev": true, + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "css-select": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/css-select/download/css-select-2.0.2.tgz", + "integrity": "sha1-q0OGzsnh9miFVWSxfDcztDsqXt4=", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^2.1.2", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "cssesc": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/cssesc/download/cssesc-2.0.0.tgz", + "integrity": "sha1-OxO9G7HLNuG8taTc0n9UxdyzVwM=", + "dev": true + }, + "csso": { + "version": "3.5.1", + "resolved": "http://registry.npm.taobao.org/csso/download/csso-3.5.1.tgz", + "integrity": "sha1-e564vmFiiXPBsmHhadLwJACOdYs=", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha.29" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.29", + "resolved": "https://registry.npm.taobao.org/css-tree/download/css-tree-1.0.0-alpha.29.tgz", + "integrity": "sha1-P6nU7zFCy9HDAedmTB81K9gvWjk=", + "dev": true, + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "http://registry.npm.taobao.org/domutils/download/domutils-1.7.0.tgz", + "integrity": "sha1-Vuo0HoNOBuZ0ivehyyXaZ+qfjCo=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/esprima/download/esprima-4.0.1.tgz", + "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=", + "dev": true + }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/is-svg/download/is-svg-3.0.0.tgz", + "integrity": "sha1-kyHb0pwhLlypnE+peUxxS8r6L3U=", + "dev": true, + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "http://registry.npm.taobao.org/js-yaml/download/js-yaml-3.13.1.tgz", + "integrity": "sha1-r/FRswv9+o5J4F2iLnQV6d+jeEc=", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "mdn-data": { + "version": "1.1.4", + "resolved": "https://registry.npm.taobao.org/mdn-data/download/mdn-data-1.1.4.tgz?cache=0&sync_timestamp=1562673334420&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmdn-data%2Fdownload%2Fmdn-data-1.1.4.tgz", + "integrity": "sha1-ULXU/8RXUnZXPE7tuHgIEqhBnwE=", + "dev": true + }, + "normalize-url": { + "version": "3.3.0", + "resolved": "http://registry.npm.taobao.org/normalize-url/download/normalize-url-3.3.0.tgz", + "integrity": "sha1-suHE3E98bVd0PfczpPWXjRhlBVk=", + "dev": true + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz", + "integrity": "sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-calc": { + "version": "7.0.1", + "resolved": "http://registry.npm.taobao.org/postcss-calc/download/postcss-calc-7.0.1.tgz", + "integrity": "sha1-Ntd7qwI7Dsu5eJ2E3LI8SUEUVDY=", + "dev": true, + "requires": { + "css-unit-converter": "^1.1.1", + "postcss": "^7.0.5", + "postcss-selector-parser": "^5.0.0-rc.4", + "postcss-value-parser": "^3.3.1" + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "http://registry.npm.taobao.org/postcss-colormin/download/postcss-colormin-4.0.3.tgz", + "integrity": "sha1-rgYLzpPteUrHEmTwgTLVUJVr04E=", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/postcss-convert-values/download/postcss-convert-values-4.0.1.tgz", + "integrity": "sha1-yjgT7U2g+BL51DcDWE5Enr4Ymn8=", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-discard-comments/download/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha1-H7q9LCRr/2qq15l7KwkY9NevQDM=", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-discard-duplicates/download/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha1-P+EzzTyCKC5VD8myORdqkge3hOs=", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/postcss-discard-empty/download/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha1-yMlR6fc+2UKAGUWERKAq2Qu592U=", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/postcss-discard-overridden/download/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha1-ZSrvipZybwKfXj4AFG7npOdV/1c=", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "http://registry.npm.taobao.org/postcss-merge-longhand/download/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha1-YvSaE+Sg7gTnuY9CuxYGLKJUniQ=", + "dev": true, + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + } + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "http://registry.npm.taobao.org/postcss-merge-rules/download/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha1-NivqT/Wh+Y5AdacTxsslrv75plA=", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "http://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-minify-font-values/download/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha1-zUw0TM5HQ0P6xdgiBqssvLiv1aY=", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-minify-gradients/download/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha1-k7KcL/UJnFNe7NpWxKpuZlpmNHE=", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-minify-params/download/postcss-minify-params-4.0.2.tgz", + "integrity": "sha1-a5zvAwwR41Jh+V9hjJADbWgNuHQ=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-minify-selectors/download/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha1-4uXrQL/uUA0M2SQ1APX46kJi+9g=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "http://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/postcss-normalize-charset/download/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha1-izWt067oOhNrBHHg1ZvlilAoXdQ=", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/postcss-normalize-url/download/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha1-EOQ3+GvHx+WPe5ZS7YeNqqlfquE=", + "dev": true, + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "http://registry.npm.taobao.org/postcss-ordered-values/download/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha1-DPdcgg7H1cTSgBiVWeC1ceusDu4=", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "http://registry.npm.taobao.org/postcss-reduce-initial/download/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha1-f9QuvqXpyBRgljniwuhK4nC6SN8=", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-reduce-transforms/download/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha1-F++kBerMbge+NBSlyi0QdGgdTik=", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "http://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha1-JJBENWaXsztk8aj3yAki3d7nGVw=", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-svgo": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-svgo/download/postcss-svgo-4.0.2.tgz", + "integrity": "sha1-F7mXvHEbMzurFDqu07jT1uPTglg=", + "dev": true, + "requires": { + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + } + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/postcss-unique-selectors/download/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha1-lEaRHzKJv9ZMbWgPBzwDsfnuS6w=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "svgo": { + "version": "1.3.0", + "resolved": "https://registry.npm.taobao.org/svgo/download/svgo-1.3.0.tgz", + "integrity": "sha1-uuUbqV3tmjOja3xGzpw1mukVQxM=", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.33", + "csso": "^3.5.1", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + } + } + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "http://registry.npm.taobao.org/cssnano-util-get-arguments/download/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", + "dev": true + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "http://registry.npm.taobao.org/cssnano-util-get-match/download/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", + "dev": true + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/cssnano-util-raw-cache/download/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha1-sm1f1fcqEd/np4RvtMZyYPlr8oI=", + "dev": true, + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz", + "integrity": "sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/cssnano-util-same-parent/download/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha1-V0CC+yhZ0ttDOFWDXZqEVuoYu/M=", + "dev": true + }, + "csso": { + "version": "2.3.2", + "resolved": "http://registry.npm.taobao.org/csso/download/csso-2.3.2.tgz", + "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", + "dev": true, + "requires": { + "clap": "^1.0.9", + "source-map": "^0.5.3" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "cuint": { + "version": "0.2.2", + "resolved": "http://registry.npm.taobao.org/cuint/download/cuint-0.2.2.tgz", + "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "http://registry.npm.taobao.org/currently-unhandled/download/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "cyclist": { + "version": "0.2.2", + "resolved": "http://registry.npm.taobao.org/cyclist/download/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/d/download/d-1.0.1.tgz", + "integrity": "sha1-hpgJU3LVjb7jRv/Qxwk/mfj561o=", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "http://registry.npm.taobao.org/date-now/download/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "de-indent": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/de-indent/download/de-indent-1.0.2.tgz", + "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "http://registry.npm.taobao.org/decode-uri-component/download/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/deep-equal/download/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "http://registry.npm.taobao.org/deep-is/download/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/define-properties/download/define-properties-1.1.3.tgz", + "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-2.0.2.tgz", + "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-6.0.2.tgz", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "dev": true + } + } + }, + "defined": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/defined/download/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/del/download/del-3.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdel%2Fdownload%2Fdel-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/globby/download/globby-6.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fglobby%2Fdownload%2Fglobby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + } + } + }, + "depd": { + "version": "1.1.2", + "resolved": "http://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/des.js/download/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "http://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npm.taobao.org/detect-indent/download/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "detect-node": { + "version": "2.0.4", + "resolved": "http://registry.npm.taobao.org/detect-node/download/detect-node-2.0.4.tgz", + "integrity": "sha1-AU7o+PZpxcWAI9pkuBecCDooxGw=", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "http://registry.npm.taobao.org/diffie-hellman/download/diffie-hellman-5.0.3.tgz", + "integrity": "sha1-QOjumPVaIUlgcUaSHGPhrl89KHU=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npm.taobao.org/dir-glob/download/dir-glob-2.2.2.tgz", + "integrity": "sha1-+gnwaUFTyJGLGLoN6vrpR2n8UMQ=", + "dev": true, + "requires": { + "path-type": "^3.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/dns-equal/download/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "http://registry.npm.taobao.org/dns-packet/download/dns-packet-1.3.1.tgz", + "integrity": "sha1-EqpCaYEHW+UAuRDu3NC0fdfe2lo=", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/dns-txt/download/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "http://registry.npm.taobao.org/doctrine/download/doctrine-2.1.0.tgz", + "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "http://registry.npm.taobao.org/dom-converter/download/dom-converter-0.2.0.tgz", + "integrity": "sha1-ZyGp2u4uKTaClVtq/kFncWJ7t2g=", + "dev": true, + "requires": { + "utila": "~0.4" + } + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "http://registry.npm.taobao.org/dom-serializer/download/dom-serializer-0.1.1.tgz", + "integrity": "sha1-HsQFnihLq+027sKUHUqXChic58A=", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/domain-browser/download/domain-browser-1.2.0.tgz", + "integrity": "sha1-PTH1AZGmdJ3RN1p/Ui6CPULlTto=", + "dev": true + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "http://registry.npm.taobao.org/domelementtype/download/domelementtype-1.3.1.tgz", + "integrity": "sha1-0EjESzew0Qp/Kj1f7j9DM9eQSB8=", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "http://registry.npm.taobao.org/domhandler/download/domhandler-2.4.2.tgz", + "integrity": "sha1-iAUJfpM9ZehVRvcm1g9euItE+AM=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "http://registry.npm.taobao.org/domutils/download/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npm.taobao.org/dot-prop/download/dot-prop-4.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdot-prop%2Fdownload%2Fdot-prop-4.2.0.tgz", + "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "http://registry.npm.taobao.org/duplexer/download/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npm.taobao.org/duplexify/download/duplexify-3.7.1.tgz", + "integrity": "sha1-Kk31MX9sz9kfhtb9JdjYoQO4gwk=", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "ejs": { + "version": "2.6.2", + "resolved": "https://registry.npm.taobao.org/ejs/download/ejs-2.6.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fejs%2Fdownload%2Fejs-2.6.2.tgz", + "integrity": "sha1-OjLGPRzRbREmbNRwOxT+xOdKtPY=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.191", + "resolved": "https://registry.npm.taobao.org/electron-to-chromium/download/electron-to-chromium-1.3.191.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron-to-chromium%2Fdownload%2Felectron-to-chromium-1.3.191.tgz", + "integrity": "sha1-xFG0Is2LLquE3tq6tavK4ervtvA=", + "dev": true + }, + "elliptic": { + "version": "6.5.0", + "resolved": "https://registry.npm.taobao.org/elliptic/download/elliptic-6.5.0.tgz", + "integrity": "sha1-K47UyJG33jIA4UQSpbgkjHr1Bco=", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/emojis-list/download/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "http://registry.npm.taobao.org/end-of-stream/download/end-of-stream-1.4.1.tgz", + "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "3.4.1", + "resolved": "https://registry.npm.taobao.org/enhanced-resolve/download/enhanced-resolve-3.4.1.tgz", + "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "object-assign": "^4.0.1", + "tapable": "^0.2.7" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "http://registry.npm.taobao.org/entities/download/entities-1.1.2.tgz", + "integrity": "sha1-vfpzUplmTfr9NFKe1PhSKidf6lY=", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "http://registry.npm.taobao.org/errno/download/errno-0.1.7.tgz", + "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "http://registry.npm.taobao.org/error-ex/download/error-ex-1.3.2.tgz", + "integrity": "sha1-tKxAZIEH/c3PriQvQovqihTU8b8=", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "error-stack-parser": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/error-stack-parser/download/error-stack-parser-2.0.2.tgz", + "integrity": "sha1-Sujbqiv5CotFBwe5FJ3KvKE1Ug0=", + "dev": true, + "requires": { + "stackframe": "^1.0.4" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "http://registry.npm.taobao.org/es-abstract/download/es-abstract-1.13.0.tgz", + "integrity": "sha1-rIYUX91QmdjdSVWMy6Lq+biOJOk=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/es-to-primitive/download/es-to-primitive-1.2.0.tgz", + "integrity": "sha1-7fckeAM0VujdqO8J4ArZZQcH83c=", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.50", + "resolved": "https://registry.npm.taobao.org/es5-ext/download/es5-ext-0.10.50.tgz", + "integrity": "sha1-bQ4joKvbJwGOWsT9CbQSvFUXp3g=", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "http://registry.npm.taobao.org/es6-iterator/download/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "http://registry.npm.taobao.org/es6-map/download/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "http://registry.npm.taobao.org/es6-set/download/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "http://registry.npm.taobao.org/es6-symbol/download/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npm.taobao.org/es6-weak-map/download/es6-weak-map-2.0.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes6-weak-map%2Fdownload%2Fes6-weak-map-2.0.3.tgz", + "integrity": "sha1-ttofFswswNm+Q+a9v8Xn383zHVM=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "http://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escope": { + "version": "3.6.0", + "resolved": "http://registry.npm.taobao.org/escope/download/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint": { + "version": "4.19.1", + "resolved": "https://registry.npm.taobao.org/eslint/download/eslint-4.19.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint%2Fdownload%2Feslint-4.19.1.tgz", + "integrity": "sha1-MtHWU+HZBAiFS/spbwdux+GGowA=", + "dev": true, + "requires": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.4", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "http://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/esprima/download/esprima-4.0.1.tgz", + "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=", + "dev": true + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npm.taobao.org/globals/download/globals-11.12.0.tgz", + "integrity": "sha1-q4eVM4hooLq9hSV1gBjCp+uVxC4=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "http://registry.npm.taobao.org/js-yaml/download/js-yaml-3.13.1.tgz", + "integrity": "sha1-r/FRswv9+o5J4F2iLnQV6d+jeEc=", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "eslint-config-standard": { + "version": "10.2.1", + "resolved": "https://registry.npm.taobao.org/eslint-config-standard/download/eslint-config-standard-10.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-config-standard%2Fdownload%2Feslint-config-standard-10.2.1.tgz", + "integrity": "sha1-wGHk0GbzedwXzVYsZOgZtN1FRZE=", + "dev": true + }, + "eslint-friendly-formatter": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/eslint-friendly-formatter/download/eslint-friendly-formatter-3.0.0.tgz", + "integrity": "sha1-J4h0Q1psRuwdlPoLH/SU4w7wQpA=", + "dev": true, + "requires": { + "chalk": "^1.0.0", + "coalescy": "1.0.0", + "extend": "^3.0.0", + "minimist": "^1.2.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "http://registry.npm.taobao.org/eslint-import-resolver-node/download/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha1-WPFfuDm40FdsqYBBNHaqskcttmo=", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + } + }, + "eslint-loader": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/eslint-loader/download/eslint-loader-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-loader%2Fdownload%2Feslint-loader-1.9.0.tgz", + "integrity": "sha1-fhvp/t3KMo09z67xrUnVvv/oOhM=", + "dev": true, + "requires": { + "loader-fs-cache": "^1.0.0", + "loader-utils": "^1.0.2", + "object-assign": "^4.0.1", + "object-hash": "^1.1.4", + "rimraf": "^2.6.1" + } + }, + "eslint-module-utils": { + "version": "2.4.0", + "resolved": "https://registry.npm.taobao.org/eslint-module-utils/download/eslint-module-utils-2.4.0.tgz", + "integrity": "sha1-i5NJnpsA6rgMy2YU5p8DZ46E4Jo=", + "dev": true, + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^2.0.0" + } + }, + "eslint-plugin-import": { + "version": "2.18.0", + "resolved": "https://registry.npm.taobao.org/eslint-plugin-import/download/eslint-plugin-import-2.18.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-plugin-import%2Fdownload%2Feslint-plugin-import-2.18.0.tgz", + "integrity": "sha1-eluo0yYi+zXrnI2xlcIJC9GKNng=", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.0", + "has": "^1.0.3", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "read-pkg-up": "^2.0.0", + "resolve": "^1.11.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "http://registry.npm.taobao.org/doctrine/download/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + } + } + }, + "eslint-plugin-node": { + "version": "5.2.1", + "resolved": "https://registry.npm.taobao.org/eslint-plugin-node/download/eslint-plugin-node-5.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-plugin-node%2Fdownload%2Feslint-plugin-node-5.2.1.tgz", + "integrity": "sha1-gN8yU8TXkBBF7If6ZgooTjK9yik=", + "dev": true, + "requires": { + "ignore": "^3.3.6", + "minimatch": "^3.0.4", + "resolve": "^1.3.3", + "semver": "5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npm.taobao.org/semver/download/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, + "eslint-plugin-promise": { + "version": "3.8.0", + "resolved": "https://registry.npm.taobao.org/eslint-plugin-promise/download/eslint-plugin-promise-3.8.0.tgz", + "integrity": "sha1-ZevyeoRePB6db2pWIt3TgBaUtiE=", + "dev": true + }, + "eslint-plugin-standard": { + "version": "3.1.0", + "resolved": "http://registry.npm.taobao.org/eslint-plugin-standard/download/eslint-plugin-standard-3.1.0.tgz", + "integrity": "sha1-Kp4hJZukxHwC1TstDJE11LECLUc=", + "dev": true + }, + "eslint-plugin-vue": { + "version": "4.7.1", + "resolved": "https://registry.npm.taobao.org/eslint-plugin-vue/download/eslint-plugin-vue-4.7.1.tgz?cache=0&sync_timestamp=1561378693439&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-plugin-vue%2Fdownload%2Feslint-plugin-vue-4.7.1.tgz", + "integrity": "sha1-yCm5/GJYLBiXtaC5Sv1E7MpRHmM=", + "dev": true, + "requires": { + "vue-eslint-parser": "^2.0.3" + } + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "http://registry.npm.taobao.org/eslint-scope/download/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/eslint-visitor-keys/download/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npm.taobao.org/espree/download/espree-3.5.4.tgz", + "integrity": "sha1-sPRHGHyKi+2US4FaZgvd9d610ac=", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "http://registry.npm.taobao.org/esprima/download/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/esquery/download/esquery-1.0.1.tgz", + "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "http://registry.npm.taobao.org/esrecurse/download/esrecurse-4.2.1.tgz", + "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "http://registry.npm.taobao.org/estraverse/download/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/esutils/download/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "http://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "http://registry.npm.taobao.org/event-emitter/download/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npm.taobao.org/eventemitter3/download/eventemitter3-3.1.2.tgz", + "integrity": "sha1-LT1I+cNGaY/Og6hdfWZOmFNd9uc=", + "dev": true + }, + "events": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/events/download/events-3.0.0.tgz", + "integrity": "sha1-mgoN+vYok9krh1uPJpjKQRSXPog=", + "dev": true + }, + "eventsource": { + "version": "0.1.6", + "resolved": "http://registry.npm.taobao.org/eventsource/download/eventsource-0.1.6.tgz", + "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", + "dev": true, + "requires": { + "original": ">=0.0.5" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "http://registry.npm.taobao.org/evp_bytestokey/download/evp_bytestokey-1.0.3.tgz", + "integrity": "sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npm.taobao.org/execa/download/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "http://registry.npm.taobao.org/expand-brackets/download/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npm.taobao.org/express/download/express-4.17.1.tgz", + "integrity": "sha1-RJH8OGBc9R+GKdOcK10Cb5ikwTQ=", + "dev": true, + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "http://registry.npm.taobao.org/extend/download/extend-3.0.2.tgz", + "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/is-extendable/download/is-extendable-1.0.1.tgz", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npm.taobao.org/external-editor/download/external-editor-2.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fexternal-editor%2Fdownload%2Fexternal-editor-2.2.0.tgz", + "integrity": "sha1-BFURz9jRM/OEZnPRBHwVTiFK09U=", + "dev": true, + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "http://registry.npm.taobao.org/extglob/download/extglob-2.0.4.tgz", + "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-6.0.2.tgz", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "dev": true + } + } + }, + "extract-text-webpack-plugin": { + "version": "3.0.2", + "resolved": "https://registry.npm.taobao.org/extract-text-webpack-plugin/download/extract-text-webpack-plugin-3.0.2.tgz", + "integrity": "sha1-XwQ+qgL5dQqSWLeMCm4NwUCPsvc=", + "dev": true, + "requires": { + "async": "^2.4.1", + "loader-utils": "^1.1.0", + "schema-utils": "^0.3.0", + "webpack-sources": "^1.0.1" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-1.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffast-deep-equal%2Fdownload%2Ffast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/fast-json-stable-stringify/download/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "http://registry.npm.taobao.org/fast-levenshtein/download/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastparse": { + "version": "1.1.2", + "resolved": "http://registry.npm.taobao.org/fastparse/download/fastparse-1.1.2.tgz", + "integrity": "sha1-kXKMWllC7O2FMSg8eUQe5BIsNak=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npm.taobao.org/faye-websocket/download/faye-websocket-0.10.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffaye-websocket%2Fdownload%2Ffaye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/figures/download/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/file-entry-cache/download/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "file-loader": { + "version": "1.1.11", + "resolved": "https://registry.npm.taobao.org/file-loader/download/file-loader-1.1.11.tgz", + "integrity": "sha1-b+iGRJsPKpNuQ8q6rAzb+zaVBvg=", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "schema-utils": "^0.4.5" + }, + "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.10.2.tgz?cache=0&sync_timestamp=1563141444360&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.10.2.tgz", + "integrity": "sha1-086gTWsBeyiUrWkED+yLYj60vVI=", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npm.taobao.org/ajv-keywords/download/ajv-keywords-3.4.1.tgz", + "integrity": "sha1-75FuJxxkrBIXH9g4TqrmsjRYVNo=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-2.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffast-deep-equal%2Fdownload%2Ffast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "http://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "dev": true + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "http://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz", + "integrity": "sha1-unT1l9K+LqiAExdG7hfQoJPGgYc=", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "filesize": { + "version": "3.6.1", + "resolved": "http://registry.npm.taobao.org/filesize/download/filesize-3.6.1.tgz", + "integrity": "sha1-CQuz7gG2+AGoqL6Z0xcQs0Irsxc=", + "dev": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "http://registry.npm.taobao.org/fill-range/download/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/finalhandler/download/finalhandler-1.1.2.tgz", + "integrity": "sha1-t+fQAP/RGTjQ/bBTUG9uur6fWH0=", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/find-cache-dir/download/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/find-up/download/find-up-2.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-up%2Fdownload%2Ffind-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "http://registry.npm.taobao.org/flat-cache/download/flat-cache-1.3.4.tgz", + "integrity": "sha1-LC73dSXMKSkAff/6HdMUqpyd7m8=", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "flatten": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/flatten/download/flatten-1.0.2.tgz", + "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/flush-write-stream/download/flush-write-stream-1.1.1.tgz", + "integrity": "sha1-jdfYc6G6vCB9lOrQwuDkQnbr8ug=", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "follow-redirects": { + "version": "1.7.0", + "resolved": "http://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.7.0.tgz", + "integrity": "sha1-SJ68GY3A5/ZBZ70jsDxMGbV4THY=", + "dev": true, + "requires": { + "debug": "^3.2.6" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "http://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/for-in/download/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forwarded": { + "version": "0.1.2", + "resolved": "http://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "http://registry.npm.taobao.org/fragment-cache/download/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "http://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "friendly-errors-webpack-plugin": { + "version": "1.7.0", + "resolved": "https://registry.npm.taobao.org/friendly-errors-webpack-plugin/download/friendly-errors-webpack-plugin-1.7.0.tgz", + "integrity": "sha1-78hsu4FiJFZYYaG+ep2E0Kr+oTY=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "error-stack-parser": "^2.0.0", + "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "from2": { + "version": "2.3.0", + "resolved": "http://registry.npm.taobao.org/from2/download/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "http://registry.npm.taobao.org/fs-write-stream-atomic/download/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/fs.realpath/download/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npm.taobao.org/fsevents/download/fsevents-1.2.9.tgz", + "integrity": "sha1-P17WZYPM1vQAtaANtvfoYTY+OI8=", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz", + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/functional-red-black-tree/download/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "http://registry.npm.taobao.org/get-caller-file/download/get-caller-file-1.0.3.tgz", + "integrity": "sha1-+Xj6TJDR3+f/LWvtoqUV5xO9z0o=", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npm.taobao.org/get-stdin/download/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/get-stream/download/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "http://registry.npm.taobao.org/get-value/download/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npm.taobao.org/glob/download/glob-7.1.4.tgz", + "integrity": "sha1-qmCKL2xXetNX4a5aXCbZqNGWklU=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "http://registry.npm.taobao.org/glob-parent/download/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "http://registry.npm.taobao.org/is-glob/download/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npm.taobao.org/globals/download/globals-9.18.0.tgz", + "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", + "dev": true + }, + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npm.taobao.org/globby/download/globby-7.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fglobby%2Fdownload%2Fglobby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.0", + "resolved": "https://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.2.0.tgz", + "integrity": "sha1-jY/cc5d8sEEEchy1NmbBymTNMos=", + "dev": true + }, + "growly": { + "version": "1.3.0", + "resolved": "http://registry.npm.taobao.org/growly/download/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, + "gzip-size": { + "version": "4.1.0", + "resolved": "https://registry.npm.taobao.org/gzip-size/download/gzip-size-4.1.0.tgz", + "integrity": "sha1-iuCWJX6r59acRb4rZ8RIEk/7UXw=", + "dev": true, + "requires": { + "duplexer": "^0.1.1", + "pify": "^3.0.0" + } + }, + "handle-thing": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/handle-thing/download/handle-thing-2.0.0.tgz", + "integrity": "sha1-DgOWlf9QyT/CiFV9aW88HcZ3Z1Q=", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "http://registry.npm.taobao.org/has/download/has-1.0.3.tgz", + "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/has-ansi/download/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-symbols/download/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-value/download/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-values/download/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "http://registry.npm.taobao.org/hash-base/download/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/hash-sum/download/hash-sum-1.0.2.tgz", + "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=", + "dev": true + }, + "hash.js": { + "version": "1.1.7", + "resolved": "http://registry.npm.taobao.org/hash.js/download/hash.js-1.1.7.tgz", + "integrity": "sha1-C6vKU46NTuSg+JiNaIZlN6ADz0I=", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/he/download/he-1.2.0.tgz", + "integrity": "sha1-hK5l+n6vsWX922FWauFLrwVmTw8=", + "dev": true + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/hex-color-regex/download/hex-color-regex-1.1.0.tgz", + "integrity": "sha1-TAb8y0YC/iYCs8k9+C1+fb8aio4=", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/hmac-drbg/download/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/home-or-tmp/download/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "http://registry.npm.taobao.org/hosted-git-info/download/hosted-git-info-2.7.1.tgz", + "integrity": "sha1-l/I2l3vW4SVAiTD/bePuxigewEc=", + "dev": true + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "http://registry.npm.taobao.org/hpack.js/download/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/hsl-regex/download/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", + "dev": true + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/hsla-regex/download/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", + "dev": true + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "http://registry.npm.taobao.org/html-comment-regex/download/html-comment-regex-1.1.2.tgz", + "integrity": "sha1-l9RoiutcgYhqNk+qDK0d2hTUM6c=", + "dev": true + }, + "html-entities": { + "version": "1.2.1", + "resolved": "http://registry.npm.taobao.org/html-entities/download/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true + }, + "html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npm.taobao.org/html-minifier/download/html-minifier-3.5.21.tgz", + "integrity": "sha1-0AQOBUcw41TbAIRjWTGUAVIS0gw=", + "dev": true, + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + } + }, + "html-webpack-plugin": { + "version": "2.30.1", + "resolved": "https://registry.npm.taobao.org/html-webpack-plugin/download/html-webpack-plugin-2.30.1.tgz", + "integrity": "sha1-f5xCG36pHsRg9WUn1430hO51N9U=", + "dev": true, + "requires": { + "bluebird": "^3.4.7", + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "toposort": "^1.0.0" + }, + "dependencies": { + "big.js": { + "version": "3.2.0", + "resolved": "http://registry.npm.taobao.org/big.js/download/big.js-3.2.0.tgz", + "integrity": "sha1-pfwpi4G54Nyi5FiCR4S2XFK6WI4=", + "dev": true + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "http://registry.npm.taobao.org/loader-utils/download/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + } + } + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "http://registry.npm.taobao.org/htmlparser2/download/htmlparser2-3.10.1.tgz", + "integrity": "sha1-vWedw/WYl7ajS7EHSchVu1OpOS8=", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-3.4.0.tgz", + "integrity": "sha1-pRwmdUZY4KPCHb9ZFjvUW6b0R/w=", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "http://registry.npm.taobao.org/http-deceiver/download/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npm.taobao.org/http-errors/download/http-errors-1.7.2.tgz", + "integrity": "sha1-T1ApzxMjnzEDblsuVSkrz7zIXI8=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz?cache=0&sync_timestamp=1560975547815&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.4.10", + "resolved": "https://registry.npm.taobao.org/http-parser-js/download/http-parser-js-0.4.10.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-parser-js%2Fdownload%2Fhttp-parser-js-0.4.10.tgz", + "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "dev": true + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "http://registry.npm.taobao.org/http-proxy/download/http-proxy-1.17.0.tgz", + "integrity": "sha1-etOElGWPhGBeL220Q230EPTlvpo=", + "dev": true, + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npm.taobao.org/http-proxy-middleware/download/http-proxy-middleware-0.19.1.tgz?cache=0&sync_timestamp=1562701920015&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-proxy-middleware%2Fdownload%2Fhttp-proxy-middleware-0.19.1.tgz", + "integrity": "sha1-GDx9xKoUeRUDBkmMIQza+WCApDo=", + "dev": true, + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/https-browserify/download/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz?cache=0&sync_timestamp=1561588160612&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ficonv-lite%2Fdownload%2Ficonv-lite-0.4.24.tgz", + "integrity": "sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/icss-replace-symbols/download/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, + "icss-utils": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/icss-utils/download/icss-utils-2.1.0.tgz", + "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "dev": true, + "requires": { + "postcss": "^6.0.1" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "http://registry.npm.taobao.org/ieee754/download/ieee754-1.1.13.tgz", + "integrity": "sha1-7BaFWOlaoYH9h9N/VcMrvLZwi4Q=", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "http://registry.npm.taobao.org/iferr/download/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npm.taobao.org/ignore/download/ignore-3.3.10.tgz", + "integrity": "sha1-Cpf7h2mG6AgcYxFg+PnziRV/AEM=", + "dev": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/import-cwd/download/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/import-fresh/download/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "dependencies": { + "caller-path": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/caller-path/download/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/resolve-from/download/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/import-from/download/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/resolve-from/download/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "import-local": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/import-local/download/import-local-1.0.0.tgz", + "integrity": "sha1-Xk/9wD9P5sAJxnKb6yljHC+CJ7w=", + "dev": true, + "requires": { + "pkg-dir": "^2.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "http://registry.npm.taobao.org/imurmurhash/download/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/indent-string/download/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/indexes-of/download/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "http://registry.npm.taobao.org/inflight/download/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz?cache=0&sync_timestamp=1560975547815&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.4.tgz", + "integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npm.taobao.org/inquirer/download/inquirer-3.3.0.tgz", + "integrity": "sha1-ndLyrXZdyrH/BEO0kUQqILoifck=", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "internal-ip": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/internal-ip/download/internal-ip-1.2.0.tgz", + "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=", + "dev": true, + "requires": { + "meow": "^3.3.0" + } + }, + "interpret": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/interpret/download/interpret-1.2.0.tgz", + "integrity": "sha1-1QYaYiS+WOgIOYX1AU2EQ1lXYpY=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "http://registry.npm.taobao.org/invariant/download/invariant-2.2.4.tgz", + "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/invert-kv/download/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "ip": { + "version": "1.1.5", + "resolved": "http://registry.npm.taobao.org/ip/download/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "http://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.9.0.tgz", + "integrity": "sha1-N9905DCg5HVQ/lSi3v4w2KzZX2U=", + "dev": true + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/is-absolute-url/download/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "http://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "http://registry.npm.taobao.org/is-arrayish/download/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/is-binary-path/download/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "http://registry.npm.taobao.org/is-buffer/download/is-buffer-1.1.6.tgz", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "http://registry.npm.taobao.org/is-callable/download/is-callable-1.1.4.tgz", + "integrity": "sha1-HhrfIZ4e62hNaR+dagX/DTCiTXU=", + "dev": true + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/is-color-stop/download/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "dev": true, + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "http://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/is-date-object/download/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "http://registry.npm.taobao.org/is-descriptor/download/is-descriptor-0.1.6.tgz", + "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-5.1.0.tgz", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "http://registry.npm.taobao.org/is-directory/download/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "http://registry.npm.taobao.org/is-extendable/download/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/is-extglob/download/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/is-finite/download/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/is-glob/download/is-glob-4.0.1.tgz", + "integrity": "sha1-dWfb6fL14kZ7x3q4PEopSCQHpdw=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/is-number/download/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/is-obj/download/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/is-path-cwd/download/is-path-cwd-1.0.0.tgz?cache=0&sync_timestamp=1562347283002&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-path-cwd%2Fdownload%2Fis-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/is-path-in-cwd/download/is-path-in-cwd-1.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-path-in-cwd%2Fdownload%2Fis-path-in-cwd-1.0.1.tgz", + "integrity": "sha1-WsSLNF72dTOb1sekipEhELJBz1I=", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/is-path-inside/download/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-plain-obj/download/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npm.taobao.org/is-plain-object/download/is-plain-object-2.0.4.tgz", + "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "http://registry.npm.taobao.org/is-promise/download/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "http://registry.npm.taobao.org/is-regex/download/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/is-resolvable/download/is-resolvable-1.1.0.tgz", + "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-stream/download/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-svg": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/is-svg/download/is-svg-2.1.0.tgz", + "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", + "dev": true, + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/is-symbol/download/is-symbol-1.0.2.tgz", + "integrity": "sha1-oFX2rlcZLK7jKeeoYBGLSXqVDzg=", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "http://registry.npm.taobao.org/is-utf8/download/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/is-windows/download/is-windows-1.0.2.tgz", + "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-wsl/download/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz?cache=0&sync_timestamp=1562592096220&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fisarray%2Fdownload%2Fisarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/isexe/download/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npm.taobao.org/isobject/download/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "js-base64": { + "version": "2.5.1", + "resolved": "http://registry.npm.taobao.org/js-base64/download/js-base64-2.5.1.tgz", + "integrity": "sha1-Hvo57yxfeYC7F4St5KivLeMpESE=", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npm.taobao.org/js-tokens/download/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.7.0", + "resolved": "http://registry.npm.taobao.org/js-yaml/download/js-yaml-3.7.0.tgz", + "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^2.6.0" + } + }, + "jsesc": { + "version": "1.3.0", + "resolved": "http://registry.npm.taobao.org/jsesc/download/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-loader": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/json-loader/download/json-loader-0.5.7.tgz", + "integrity": "sha1-3KFKcCNf+C8KyaOr62DTN6NlGF0=", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/json-parse-better-errors/download/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "http://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/json-stable-stringify-without-jsonify/download/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npm.taobao.org/json3/download/json3-3.3.3.tgz", + "integrity": "sha1-f8EON1/FrkLEcFpcwKpvYr4wW4E=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "http://registry.npm.taobao.org/json5/download/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "killable": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/killable/download/killable-1.0.1.tgz", + "integrity": "sha1-TIzkQRh6Bhx0dPuHygjipjgZSJI=", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "last-call-webpack-plugin": { + "version": "2.1.2", + "resolved": "http://registry.npm.taobao.org/last-call-webpack-plugin/download/last-call-webpack-plugin-2.1.2.tgz", + "integrity": "sha1-rYDG4xCZgpTS7SGApo6VieR2jEQ=", + "dev": true, + "requires": { + "lodash": "^4.17.4", + "webpack-sources": "^1.0.1" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "http://registry.npm.taobao.org/lazy-cache/download/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/lcid/download/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "http://registry.npm.taobao.org/levn/download/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/load-json-file/download/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "loader-fs-cache": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/loader-fs-cache/download/loader-fs-cache-1.0.2.tgz", + "integrity": "sha1-VM7fa3J+F3n9jwEgXwX26IcG8IY=", + "dev": true, + "requires": { + "find-cache-dir": "^0.1.1", + "mkdirp": "0.5.1" + }, + "dependencies": { + "find-cache-dir": { + "version": "0.1.1", + "resolved": "http://registry.npm.taobao.org/find-cache-dir/download/find-cache-dir-0.1.1.tgz", + "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "mkdirp": "^0.5.1", + "pkg-dir": "^1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/find-up/download/find-up-1.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-up%2Fdownload%2Ffind-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "http://registry.npm.taobao.org/path-exists/download/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/pkg-dir/download/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "^1.0.0" + } + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "http://registry.npm.taobao.org/loader-runner/download/loader-runner-2.4.0.tgz", + "integrity": "sha1-7UcGa/5TTX6ExMe5mYwqdWB9k1c=", + "dev": true + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "http://registry.npm.taobao.org/loader-utils/download/loader-utils-1.2.3.tgz", + "integrity": "sha1-H/XcaRHJ8KBiUxpMBLYJQGEIwsc=", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/json5/download/json5-1.0.1.tgz", + "integrity": "sha1-d5+wAYYE+oVOrL9iUhgNg1Q+Pb4=", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/locate-path/download/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.14", + "resolved": "https://registry.npm.taobao.org/lodash/download/lodash-4.17.14.tgz?cache=0&sync_timestamp=1562774134174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.14.tgz", + "integrity": "sha1-nOSHrmbJYlT+ILWZ8htoFgKAeLo=", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "http://registry.npm.taobao.org/lodash.camelcase/download/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "http://registry.npm.taobao.org/lodash.memoize/download/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "http://registry.npm.taobao.org/lodash.uniq/download/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npm.taobao.org/log-symbols/download/log-symbols-2.2.0.tgz", + "integrity": "sha1-V0Dhxdbw39pK2TI7UzIQfva0xAo=", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "loglevel": { + "version": "1.6.3", + "resolved": "https://registry.npm.taobao.org/loglevel/download/loglevel-1.6.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Floglevel%2Fdownload%2Floglevel-1.6.3.tgz", + "integrity": "sha1-d/LrZL5VpATJ/QStFtV8HW1rEoA=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/longest/download/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "http://registry.npm.taobao.org/loose-envify/download/loose-envify-1.4.0.tgz", + "integrity": "sha1-ce5R+nvkyuwaY4OffmgtgTLTDK8=", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npm.taobao.org/loud-rejection/download/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "http://registry.npm.taobao.org/lower-case/download/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "http://registry.npm.taobao.org/lru-cache/download/lru-cache-4.1.5.tgz", + "integrity": "sha1-i75Q6oW+1ZvJ4z3KuCNe6bz0Q80=", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "http://registry.npm.taobao.org/make-dir/download/make-dir-1.3.0.tgz", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "http://registry.npm.taobao.org/map-cache/download/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/map-obj/download/map-obj-1.0.1.tgz?cache=0&sync_timestamp=1560578867343&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmap-obj%2Fdownload%2Fmap-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/map-visit/download/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "math-expression-evaluator": { + "version": "1.2.17", + "resolved": "http://registry.npm.taobao.org/math-expression-evaluator/download/math-expression-evaluator-1.2.17.tgz", + "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "http://registry.npm.taobao.org/md5.js/download/md5.js-1.3.5.tgz", + "integrity": "sha1-tdB7jjIW4+J81yjXL3DR5qNCAF8=", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npm.taobao.org/mdn-data/download/mdn-data-2.0.4.tgz?cache=0&sync_timestamp=1562673334420&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmdn-data%2Fdownload%2Fmdn-data-2.0.4.tgz", + "integrity": "sha1-aZs8OKxvHXKAkaZGULZdOIUC/Vs=", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/mem/download/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "http://registry.npm.taobao.org/memory-fs/download/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "http://registry.npm.taobao.org/meow/download/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/find-up/download/find-up-1.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-up%2Fdownload%2Ffind-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/load-json-file/download/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "http://registry.npm.taobao.org/path-exists/download/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/path-type/download/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/read-pkg/download/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/strip-bom/download/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "http://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npm.taobao.org/micromatch/download/micromatch-3.1.10.tgz", + "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-6.0.2.tgz", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "dev": true + } + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/miller-rabin/download/miller-rabin-4.0.1.tgz", + "integrity": "sha1-8IA1HIZbDcViqEYpZtqlNUPHik0=", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz?cache=0&sync_timestamp=1560034758817&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime%2Fdownload%2Fmime-1.6.0.tgz", + "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=", + "dev": true + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npm.taobao.org/mime-db/download/mime-db-1.40.0.tgz", + "integrity": "sha1-plBX6ZjbCQ9zKmj2wnbTh9QSbDI=", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.24.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-types%2Fdownload%2Fmime-types-2.1.24.tgz", + "integrity": "sha1-tvjQs+lR77d97eyhlM/20W9nb4E=", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/mimic-fn/download/mimic-fn-1.2.0.tgz?cache=0&sync_timestamp=1560442058146&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmimic-fn%2Fdownload%2Fmimic-fn-1.2.0.tgz", + "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", + "dev": true + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/minimalistic-assert/download/minimalistic-assert-1.0.1.tgz", + "integrity": "sha1-LhlN4ERibUoQ5/f7wAznPoPk1cc=", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/minimalistic-crypto-utils/download/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "http://registry.npm.taobao.org/minimatch/download/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npm.taobao.org/minimist/download/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mississippi": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/mississippi/download/mississippi-2.0.0.tgz", + "integrity": "sha1-NEKlCPr8KFAEhv7qmUCWduTuWm8=", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npm.taobao.org/mixin-deep/download/mixin-deep-1.3.2.tgz", + "integrity": "sha1-ESC0PcNZp4Xc5ltVuC4lfM9HlWY=", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/is-extendable/download/is-extendable-1.0.1.tgz", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/move-concurrently/download/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "http://registry.npm.taobao.org/multicast-dns/download/multicast-dns-6.2.3.tgz", + "integrity": "sha1-oOx72QVcQoL3kMPIL04o2zsxsik=", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/multicast-dns-service-types/download/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "http://registry.npm.taobao.org/mute-stream/download/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npm.taobao.org/nan/download/nan-2.14.0.tgz", + "integrity": "sha1-eBj3IgJ7JFmobwKV1DTR/CM2xSw=", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "http://registry.npm.taobao.org/nanomatch/download/nanomatch-1.2.13.tgz", + "integrity": "sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-6.0.2.tgz", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "dev": true + } + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "http://registry.npm.taobao.org/natural-compare/download/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npm.taobao.org/negotiator/download/negotiator-0.6.2.tgz", + "integrity": "sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs=", + "dev": true + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npm.taobao.org/neo-async/download/neo-async-2.6.1.tgz", + "integrity": "sha1-rCetpmFn+ohJpq3dg39rGJrSCBw=", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/next-tick/download/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "no-case": { + "version": "2.3.2", + "resolved": "http://registry.npm.taobao.org/no-case/download/no-case-2.3.2.tgz", + "integrity": "sha1-YLgTOWvjmz8SiKTB7V0efSi0ZKw=", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npm.taobao.org/node-forge/download/node-forge-0.7.5.tgz", + "integrity": "sha1-bBUsNFzhHFL0ZcKr2VfoY5zWdN8=", + "dev": true + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/node-libs-browser/download/node-libs-browser-2.2.1.tgz", + "integrity": "sha1-tk9RPRgzhiX5A0bSew0jXmMfZCU=", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "http://registry.npm.taobao.org/punycode/download/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "node-notifier": { + "version": "5.4.0", + "resolved": "http://registry.npm.taobao.org/node-notifier/download/node-notifier-5.4.0.tgz", + "integrity": "sha1-e0Vf3On33gxjU4KXNU89tGhCbmo=", + "dev": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, + "node-releases": { + "version": "1.1.25", + "resolved": "https://registry.npm.taobao.org/node-releases/download/node-releases-1.1.25.tgz", + "integrity": "sha1-DC19vH/tMPvgKp7jAHuMkL8BM9M=", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "http://registry.npm.taobao.org/normalize-package-data/download/normalize-package-data-2.5.0.tgz", + "integrity": "sha1-5m2xg4sgDB38IzIl0SyzZSDiNKg=", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/normalize-path/download/normalize-path-3.0.0.tgz", + "integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "http://registry.npm.taobao.org/normalize-range/download/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "http://registry.npm.taobao.org/normalize-url/download/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/npm-run-path/download/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/nth-check/download/nth-check-1.0.2.tgz", + "integrity": "sha1-sr0pXDfj3VijvwcAN2Zjuk2c8Fw=", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "http://registry.npm.taobao.org/num2fraction/download/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/number-is-nan/download/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "http://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "http://registry.npm.taobao.org/object-copy/download/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-hash": { + "version": "1.3.1", + "resolved": "http://registry.npm.taobao.org/object-hash/download/object-hash-1.3.1.tgz", + "integrity": "sha1-/eRSCYqVHLFF8Dm7fUVUSd3BJt8=", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/object-keys/download/object-keys-1.1.1.tgz", + "integrity": "sha1-HEfyct8nfzsdrwYWd9nILiMixg4=", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/object-visit/download/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "http://registry.npm.taobao.org/object.getownpropertydescriptors/download/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "http://registry.npm.taobao.org/object.pick/download/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/object.values/download/object.values-1.1.0.tgz", + "integrity": "sha1-v2gQ712j5TJXkOqqK+IT6oRiTak=", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "http://registry.npm.taobao.org/obuf/download/obuf-1.1.2.tgz", + "integrity": "sha1-Cb6jND1BhZ69RGKS0RydTbYZCE4=", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "http://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/on-headers/download/on-headers-1.0.2.tgz", + "integrity": "sha1-dysK5qqlJcOZ5Imt+tkMQD6zwo8=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "http://registry.npm.taobao.org/once/download/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/onetime/download/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "opener": { + "version": "1.5.1", + "resolved": "http://registry.npm.taobao.org/opener/download/opener-1.5.1.tgz", + "integrity": "sha1-bS8Od/GgrwAyrKcWwsH7uOfoq+0=", + "dev": true + }, + "opn": { + "version": "5.5.0", + "resolved": "http://registry.npm.taobao.org/opn/download/opn-5.5.0.tgz", + "integrity": "sha1-/HFk+rVtI1kExRw7J9pnWMo7m/w=", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimize-css-assets-webpack-plugin": { + "version": "3.2.1", + "resolved": "https://registry.npm.taobao.org/optimize-css-assets-webpack-plugin/download/optimize-css-assets-webpack-plugin-3.2.1.tgz", + "integrity": "sha1-nRhlSg4FjAkL3ZkbBLyw9vJIZXM=", + "dev": true, + "requires": { + "cssnano": "^4.1.10", + "last-call-webpack-plugin": "^2.1.2" + }, + "dependencies": { + "cssnano": { + "version": "4.1.10", + "resolved": "http://registry.npm.taobao.org/cssnano/download/cssnano-4.1.10.tgz", + "integrity": "sha1-CsQfCxPRPUZUh+ERt3jULaYxuLI=", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz", + "integrity": "sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "http://registry.npm.taobao.org/optionator/download/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "ora": { + "version": "1.4.0", + "resolved": "https://registry.npm.taobao.org/ora/download/ora-1.4.0.tgz", + "integrity": "sha1-iERYIVs6XUCXWSKF+TMhu3p54uU=", + "dev": true, + "requires": { + "chalk": "^2.1.0", + "cli-cursor": "^2.1.0", + "cli-spinners": "^1.0.1", + "log-symbols": "^2.1.0" + } + }, + "original": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/original/download/original-1.0.2.tgz", + "integrity": "sha1-5EKmHP/hxf0gpl8yYcJmY7MD8l8=", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "http://registry.npm.taobao.org/os-browserify/download/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/os-homedir/download/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/os-locale/download/os-locale-2.1.0.tgz?cache=0&sync_timestamp=1560274285880&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fos-locale%2Fdownload%2Fos-locale-2.1.0.tgz", + "integrity": "sha1-QrwpAKa1uL0XN2yOiCtlr8zyS/I=", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/os-tmpdir/download/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/p-finally/download/p-finally-1.0.0.tgz?cache=0&sync_timestamp=1560955759606&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fp-finally%2Fdownload%2Fp-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "http://registry.npm.taobao.org/p-limit/download/p-limit-1.3.0.tgz", + "integrity": "sha1-uGvV8MJWkJEcdZD8v8IBDVSzzLg=", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/p-locate/download/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/p-map/download/p-map-1.2.0.tgz?cache=0&sync_timestamp=1563032875018&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fp-map%2Fdownload%2Fp-map-1.2.0.tgz", + "integrity": "sha1-5OlPMR6rvIYzoeeZCBZfyiYkG2s=", + "dev": true + }, + "p-try": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/p-try/download/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pako": { + "version": "1.0.10", + "resolved": "http://registry.npm.taobao.org/pako/download/pako-1.0.10.tgz", + "integrity": "sha1-Qyi621CGpCaqkPVBl31JVdpclzI=", + "dev": true + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/parallel-transform/download/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/param-case/download/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, + "parse-asn1": { + "version": "5.1.4", + "resolved": "http://registry.npm.taobao.org/parse-asn1/download/parse-asn1-5.1.4.tgz", + "integrity": "sha1-N/Zij4I/vesic7TVQENKIvPvH8w=", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npm.taobao.org/parse-json/download/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npm.taobao.org/parseurl/download/parseurl-1.3.3.tgz", + "integrity": "sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "http://registry.npm.taobao.org/pascalcase/download/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "http://registry.npm.taobao.org/path-browserify/download/path-browserify-0.0.1.tgz", + "integrity": "sha1-5sTd1+06onxoogzE5Q4aTug7vEo=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/path-dirname/download/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/path-exists/download/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/path-is-absolute/download/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/path-is-inside/download/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/path-key/download/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "http://registry.npm.taobao.org/path-parse/download/path-parse-1.0.6.tgz", + "integrity": "sha1-1i27VnlAXXLEc37FhgDp3c8G0kw=", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "http://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/path-type/download/path-type-3.0.0.tgz", + "integrity": "sha1-zvMdyOCho7sNEFwM2Xzzv0f0428=", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "http://registry.npm.taobao.org/pbkdf2/download/pbkdf2-3.0.17.tgz", + "integrity": "sha1-l2wgZTBhexTrsyEUI597CTNuk6Y=", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/pify/download/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "http://registry.npm.taobao.org/pinkie/download/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/pinkie-promise/download/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/pkg-dir/download/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npm.taobao.org/pluralize/download/pluralize-7.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpluralize%2Fdownload%2Fpluralize-7.0.0.tgz", + "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", + "dev": true + }, + "portfinder": { + "version": "1.0.21", + "resolved": "https://registry.npm.taobao.org/portfinder/download/portfinder-1.0.21.tgz?cache=0&sync_timestamp=1562941556574&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fportfinder%2Fdownload%2Fportfinder-1.0.21.tgz", + "integrity": "sha1-YOE5e5WsFwdJ23ADTs4wa5on4yQ=", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npm.taobao.org/async/download/async-1.5.2.tgz?cache=0&sync_timestamp=1563147078542&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fasync%2Fdownload%2Fasync-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "http://registry.npm.taobao.org/posix-character-classes/download/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-6.0.23.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-6.0.23.tgz", + "integrity": "sha1-YcgswyisYOZ3ZF+XkFTrmLwOMyQ=", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "postcss-calc": { + "version": "5.3.1", + "resolved": "http://registry.npm.taobao.org/postcss-calc/download/postcss-calc-5.3.1.tgz", + "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", + "dev": true, + "requires": { + "postcss": "^5.0.2", + "postcss-message-helpers": "^2.0.0", + "reduce-css-calc": "^1.2.6" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-colormin": { + "version": "2.2.2", + "resolved": "http://registry.npm.taobao.org/postcss-colormin/download/postcss-colormin-2.2.2.tgz", + "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", + "dev": true, + "requires": { + "colormin": "^1.0.5", + "postcss": "^5.0.13", + "postcss-value-parser": "^3.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-convert-values": { + "version": "2.6.1", + "resolved": "http://registry.npm.taobao.org/postcss-convert-values/download/postcss-convert-values-2.6.1.tgz", + "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", + "dev": true, + "requires": { + "postcss": "^5.0.11", + "postcss-value-parser": "^3.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-comments": { + "version": "2.0.4", + "resolved": "http://registry.npm.taobao.org/postcss-discard-comments/download/postcss-discard-comments-2.0.4.tgz", + "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", + "dev": true, + "requires": { + "postcss": "^5.0.14" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-duplicates": { + "version": "2.1.0", + "resolved": "http://registry.npm.taobao.org/postcss-discard-duplicates/download/postcss-discard-duplicates-2.1.0.tgz", + "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", + "dev": true, + "requires": { + "postcss": "^5.0.4" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-empty": { + "version": "2.1.0", + "resolved": "http://registry.npm.taobao.org/postcss-discard-empty/download/postcss-discard-empty-2.1.0.tgz", + "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", + "dev": true, + "requires": { + "postcss": "^5.0.14" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-overridden": { + "version": "0.1.1", + "resolved": "http://registry.npm.taobao.org/postcss-discard-overridden/download/postcss-discard-overridden-0.1.1.tgz", + "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", + "dev": true, + "requires": { + "postcss": "^5.0.16" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-discard-unused": { + "version": "2.2.3", + "resolved": "http://registry.npm.taobao.org/postcss-discard-unused/download/postcss-discard-unused-2.2.3.tgz", + "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", + "dev": true, + "requires": { + "postcss": "^5.0.14", + "uniqs": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-filter-plugins": { + "version": "2.0.3", + "resolved": "http://registry.npm.taobao.org/postcss-filter-plugins/download/postcss-filter-plugins-2.0.3.tgz", + "integrity": "sha1-giRf34IzcEFkXkdxFNjlk6oYuOw=", + "dev": true, + "requires": { + "postcss": "^5.0.4" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-import": { + "version": "11.1.0", + "resolved": "http://registry.npm.taobao.org/postcss-import/download/postcss-import-11.1.0.tgz", + "integrity": "sha1-Vck2LJGSmU7GiGXSJEGd8dspgfA=", + "dev": true, + "requires": { + "postcss": "^6.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-load-config": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/postcss-load-config/download/postcss-load-config-2.1.0.tgz", + "integrity": "sha1-yE1pK3u3tB3c7ZTuYuirMbQXsAM=", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-load-options": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/postcss-load-options/download/postcss-load-options-1.2.0.tgz", + "integrity": "sha1-sJixVZ3awt8EvAuzdfmaXP4rbYw=", + "dev": true, + "requires": { + "cosmiconfig": "^2.1.0", + "object-assign": "^4.1.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "2.2.2", + "resolved": "https://registry.npm.taobao.org/cosmiconfig/download/cosmiconfig-2.2.2.tgz", + "integrity": "sha1-YXPOvVb6wELB9DkO33r2wHx8uJI=", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.4.3", + "minimist": "^1.2.0", + "object-assign": "^4.1.0", + "os-homedir": "^1.0.1", + "parse-json": "^2.2.0", + "require-from-string": "^1.1.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "postcss-load-plugins": { + "version": "2.3.0", + "resolved": "http://registry.npm.taobao.org/postcss-load-plugins/download/postcss-load-plugins-2.3.0.tgz", + "integrity": "sha1-dFdoEWWZrKLwCfrUJrABdQSdjZI=", + "dev": true, + "requires": { + "cosmiconfig": "^2.1.1", + "object-assign": "^4.1.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "2.2.2", + "resolved": "https://registry.npm.taobao.org/cosmiconfig/download/cosmiconfig-2.2.2.tgz", + "integrity": "sha1-YXPOvVb6wELB9DkO33r2wHx8uJI=", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.4.3", + "minimist": "^1.2.0", + "object-assign": "^4.1.0", + "os-homedir": "^1.0.1", + "parse-json": "^2.2.0", + "require-from-string": "^1.1.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "postcss-loader": { + "version": "2.1.6", + "resolved": "http://registry.npm.taobao.org/postcss-loader/download/postcss-loader-2.1.6.tgz", + "integrity": "sha1-HX3XsXxrojS5vtWvE+C+pApC10A=", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^6.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^0.4.0" + }, + "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.10.2.tgz?cache=0&sync_timestamp=1563141444360&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.10.2.tgz", + "integrity": "sha1-086gTWsBeyiUrWkED+yLYj60vVI=", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npm.taobao.org/ajv-keywords/download/ajv-keywords-3.4.1.tgz", + "integrity": "sha1-75FuJxxkrBIXH9g4TqrmsjRYVNo=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-2.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffast-deep-equal%2Fdownload%2Ffast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "http://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "dev": true + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "http://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz", + "integrity": "sha1-unT1l9K+LqiAExdG7hfQoJPGgYc=", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "postcss-merge-idents": { + "version": "2.1.7", + "resolved": "http://registry.npm.taobao.org/postcss-merge-idents/download/postcss-merge-idents-2.1.7.tgz", + "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", + "dev": true, + "requires": { + "has": "^1.0.1", + "postcss": "^5.0.10", + "postcss-value-parser": "^3.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-merge-longhand": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-merge-longhand/download/postcss-merge-longhand-2.0.2.tgz", + "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", + "dev": true, + "requires": { + "postcss": "^5.0.4" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-merge-rules": { + "version": "2.1.2", + "resolved": "http://registry.npm.taobao.org/postcss-merge-rules/download/postcss-merge-rules-2.1.2.tgz", + "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", + "dev": true, + "requires": { + "browserslist": "^1.5.2", + "caniuse-api": "^1.5.2", + "postcss": "^5.0.4", + "postcss-selector-parser": "^2.2.2", + "vendors": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "dev": true, + "requires": { + "caniuse-db": "^1.0.30000639", + "electron-to-chromium": "^1.2.7" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-message-helpers": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/postcss-message-helpers/download/postcss-message-helpers-2.0.0.tgz", + "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", + "dev": true + }, + "postcss-minify-font-values": { + "version": "1.0.5", + "resolved": "http://registry.npm.taobao.org/postcss-minify-font-values/download/postcss-minify-font-values-1.0.5.tgz", + "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "postcss": "^5.0.4", + "postcss-value-parser": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-minify-gradients": { + "version": "1.0.5", + "resolved": "http://registry.npm.taobao.org/postcss-minify-gradients/download/postcss-minify-gradients-1.0.5.tgz", + "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", + "dev": true, + "requires": { + "postcss": "^5.0.12", + "postcss-value-parser": "^3.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-minify-params": { + "version": "1.2.2", + "resolved": "http://registry.npm.taobao.org/postcss-minify-params/download/postcss-minify-params-1.2.2.tgz", + "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.1", + "postcss": "^5.0.2", + "postcss-value-parser": "^3.0.2", + "uniqs": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-minify-selectors": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/postcss-minify-selectors/download/postcss-minify-selectors-2.1.1.tgz", + "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.2", + "has": "^1.0.1", + "postcss": "^5.0.14", + "postcss-selector-parser": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "1.2.1", + "resolved": "http://registry.npm.taobao.org/postcss-modules-extract-imports/download/postcss-modules-extract-imports-1.2.1.tgz", + "integrity": "sha1-3IfjQUjsfqtfeR981YSYMzdbdBo=", + "dev": true, + "requires": { + "postcss": "^6.0.1" + } + }, + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/postcss-modules-local-by-default/download/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/postcss-modules-scope/download/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npm.taobao.org/postcss-modules-values/download/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "dev": true, + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" + } + }, + "postcss-normalize-charset": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/postcss-normalize-charset/download/postcss-normalize-charset-1.1.1.tgz", + "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", + "dev": true, + "requires": { + "postcss": "^5.0.5" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-normalize-display-values/download/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha1-Db4EpM6QY9RmftK+R2u4MMglk1o=", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz", + "integrity": "sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-normalize-positions/download/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha1-BfdX+E8mBDc3g2ipH4ky1LECkX8=", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz", + "integrity": "sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-normalize-repeat-style/download/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha1-xOu8KJ85kaAo1EdRy90RkYsXkQw=", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz", + "integrity": "sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-normalize-string/download/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha1-zUTECrB6DHo23F6Zqs4eyk7CaQw=", + "dev": true, + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz", + "integrity": "sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-normalize-timing-functions/download/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha1-jgCcoqOUnNr4rSPmtquZy159KNk=", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz", + "integrity": "sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "http://registry.npm.taobao.org/postcss-normalize-unicode/download/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha1-hBvUj9zzAZrUuqdJOj02O1KuHPs=", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "browserslist": { + "version": "4.6.6", + "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-4.6.6.tgz", + "integrity": "sha1-bkv0Z83lILydvfN0fa+gNTHOxFM=", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000984", + "electron-to-chromium": "^1.3.191", + "node-releases": "^1.1.25" + } + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz", + "integrity": "sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-url": { + "version": "3.0.8", + "resolved": "http://registry.npm.taobao.org/postcss-normalize-url/download/postcss-normalize-url-3.0.8.tgz", + "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", + "dev": true, + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^1.4.0", + "postcss": "^5.0.14", + "postcss-value-parser": "^3.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-normalize-whitespace/download/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha1-vx1AcP5Pzqh9E0joJdjMDF+qfYI=", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz", + "integrity": "sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-ordered-values": { + "version": "2.2.3", + "resolved": "http://registry.npm.taobao.org/postcss-ordered-values/download/postcss-ordered-values-2.2.3.tgz", + "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", + "dev": true, + "requires": { + "postcss": "^5.0.4", + "postcss-value-parser": "^3.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-reduce-idents": { + "version": "2.4.0", + "resolved": "http://registry.npm.taobao.org/postcss-reduce-idents/download/postcss-reduce-idents-2.4.0.tgz", + "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", + "dev": true, + "requires": { + "postcss": "^5.0.4", + "postcss-value-parser": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-reduce-initial": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/postcss-reduce-initial/download/postcss-reduce-initial-1.0.1.tgz", + "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", + "dev": true, + "requires": { + "postcss": "^5.0.4" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-reduce-transforms": { + "version": "1.0.4", + "resolved": "http://registry.npm.taobao.org/postcss-reduce-transforms/download/postcss-reduce-transforms-1.0.4.tgz", + "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", + "dev": true, + "requires": { + "has": "^1.0.1", + "postcss": "^5.0.8", + "postcss-value-parser": "^3.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-selector-parser": { + "version": "2.2.3", + "resolved": "http://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-2.2.3.tgz", + "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "dev": true, + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-svgo": { + "version": "2.1.6", + "resolved": "http://registry.npm.taobao.org/postcss-svgo/download/postcss-svgo-2.1.6.tgz", + "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", + "dev": true, + "requires": { + "is-svg": "^2.0.0", + "postcss": "^5.0.14", + "postcss-value-parser": "^3.2.3", + "svgo": "^0.7.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-unique-selectors": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/postcss-unique-selectors/download/postcss-unique-selectors-2.0.2.tgz", + "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.1", + "postcss": "^5.0.4", + "uniqs": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-url": { + "version": "7.3.2", + "resolved": "https://registry.npm.taobao.org/postcss-url/download/postcss-url-7.3.2.tgz", + "integrity": "sha1-X+onOAf7hLOMRhw8mp6KvSNfcSA=", + "dev": true, + "requires": { + "mime": "^1.4.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.0", + "postcss": "^6.0.1", + "xxhashjs": "^0.2.1" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npm.taobao.org/postcss-value-parser/download/postcss-value-parser-3.3.1.tgz", + "integrity": "sha1-n/giVH4okyE88cMO+lGsX9G6goE=", + "dev": true + }, + "postcss-zindex": { + "version": "2.2.0", + "resolved": "http://registry.npm.taobao.org/postcss-zindex/download/postcss-zindex-2.2.0.tgz", + "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", + "dev": true, + "requires": { + "has": "^1.0.1", + "postcss": "^5.0.4", + "uniqs": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-5.2.18.tgz", + "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "http://registry.npm.taobao.org/prelude-ls/download/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npm.taobao.org/prepend-http/download/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "prettier": { + "version": "1.18.2", + "resolved": "https://registry.npm.taobao.org/prettier/download/prettier-1.18.2.tgz", + "integrity": "sha1-aCPnxZAAF7S9Os9G/prEtNe9qeo=", + "dev": true + }, + "pretty-error": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/pretty-error/download/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, + "requires": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, + "private": { + "version": "0.1.8", + "resolved": "http://registry.npm.taobao.org/private/download/private-0.1.8.tgz", + "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "http://registry.npm.taobao.org/process/download/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fprocess-nextick-args%2Fdownload%2Fprocess-nextick-args-2.0.1.tgz", + "integrity": "sha1-eCDZsWEgzFXKmud5JoCufbptf+I=", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "http://registry.npm.taobao.org/progress/download/progress-2.0.3.tgz", + "integrity": "sha1-foz42PW48jnBvGi+tOt4Vn1XLvg=", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/promise-inflight/download/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npm.taobao.org/proxy-addr/download/proxy-addr-2.0.5.tgz", + "integrity": "sha1-NMvWSi2B9LH9IedvnwbIpFKZ7jQ=", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/prr/download/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/pseudomap/download/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "http://registry.npm.taobao.org/public-encrypt/download/public-encrypt-4.0.3.tgz", + "integrity": "sha1-T8ydd6B+SLp1J+fL4N4z0HATMeA=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/pump/download/pump-2.0.1.tgz", + "integrity": "sha1-Ejma3W5M91Jtlzy8i1zi4pCLOQk=", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npm.taobao.org/pumpify/download/pumpify-1.5.1.tgz", + "integrity": "sha1-NlE74karJ1cLGjdKXOJ4v9dDcM4=", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/punycode/download/punycode-2.1.1.tgz", + "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "http://registry.npm.taobao.org/q/download/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "http://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz", + "integrity": "sha1-QdwaAV49WB8WIXdr4xr7KHapsbw=", + "dev": true + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npm.taobao.org/query-string/download/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "http://registry.npm.taobao.org/querystring/download/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "http://registry.npm.taobao.org/querystring-es3/download/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/querystringify/download/querystringify-2.1.1.tgz", + "integrity": "sha1-YOWl/WSn+L+k0qsu1v30yFutFU4=", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "http://registry.npm.taobao.org/randombytes/download/randombytes-2.1.0.tgz", + "integrity": "sha1-32+ENy8CcNxlzfYpE0mrekc9Tyo=", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "http://registry.npm.taobao.org/randomfill/download/randomfill-1.0.4.tgz", + "integrity": "sha1-ySGW/IarQr6YPxvzF3giSTHWFFg=", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npm.taobao.org/range-parser/download/range-parser-1.2.1.tgz", + "integrity": "sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE=", + "dev": true + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npm.taobao.org/raw-body/download/raw-body-2.4.0.tgz", + "integrity": "sha1-oc5vucm8NWylLoklarWQWeE9AzI=", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "read-cache": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/read-cache/download/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "requires": { + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/read-pkg/download/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + }, + "dependencies": { + "path-type": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/path-type/download/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/readdirp/download/readdirp-2.2.1.tgz?cache=0&sync_timestamp=1562457191963&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freaddirp%2Fdownload%2Freaddirp-2.2.1.tgz", + "integrity": "sha1-DodiKjMlqjPokihcr4tOhGUppSU=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "http://registry.npm.taobao.org/rechoir/download/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/redent/download/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "reduce-css-calc": { + "version": "1.3.0", + "resolved": "http://registry.npm.taobao.org/reduce-css-calc/download/reduce-css-calc-1.3.0.tgz", + "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", + "dev": true, + "requires": { + "balanced-match": "^0.4.2", + "math-expression-evaluator": "^1.2.14", + "reduce-function-call": "^1.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "http://registry.npm.taobao.org/balanced-match/download/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + } + } + }, + "reduce-function-call": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/reduce-function-call/download/reduce-function-call-1.0.2.tgz", + "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", + "dev": true, + "requires": { + "balanced-match": "^0.4.2" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "http://registry.npm.taobao.org/balanced-match/download/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + } + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "http://registry.npm.taobao.org/regenerate/download/regenerate-1.4.0.tgz", + "integrity": "sha1-SoVuxLVuQHfFV1icroXnpMiGmhE=", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "http://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.11.1.tgz", + "integrity": "sha1-vgWtf5v30i4Fb5cmzuUBf78Z4uk=", + "dev": true + }, + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npm.taobao.org/regenerator-transform/download/regenerator-transform-0.10.1.tgz", + "integrity": "sha1-HkmWg3Ix2ot/PPQRTXG1aRoGgN0=", + "dev": true, + "requires": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/regex-not/download/regex-not-1.0.2.tgz", + "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpp": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/regexpp/download/regexpp-1.1.0.tgz", + "integrity": "sha1-DjUW3Qt5BPQT0tQZPc5GGMOmias=", + "dev": true + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/regexpu-core/download/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "http://registry.npm.taobao.org/regjsgen/download/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "http://registry.npm.taobao.org/regjsparser/download/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "http://registry.npm.taobao.org/jsesc/download/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "http://registry.npm.taobao.org/relateurl/download/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/remove-trailing-separator/download/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "renderkid": { + "version": "2.0.3", + "resolved": "http://registry.npm.taobao.org/renderkid/download/renderkid-2.0.3.tgz", + "integrity": "sha1-OAF5wv9a4TZcUivy/Pz/AcW3QUk=", + "dev": true, + "requires": { + "css-select": "^1.1.0", + "dom-converter": "^0.2", + "htmlparser2": "^3.3.0", + "strip-ansi": "^3.0.0", + "utila": "^0.4.0" + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/repeat-element/download/repeat-element-1.1.3.tgz", + "integrity": "sha1-eC4NglwMWjuzlzH4Tv7mt0Lmsc4=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "http://registry.npm.taobao.org/repeat-string/download/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/repeating/download/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/require-directory/download/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "1.2.1", + "resolved": "http://registry.npm.taobao.org/require-from-string/download/require-from-string-1.2.1.tgz", + "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/require-main-filename/download/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "http://registry.npm.taobao.org/require-uncached/download/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/requires-port/download/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.11.1", + "resolved": "https://registry.npm.taobao.org/resolve/download/resolve-1.11.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve%2Fdownload%2Fresolve-1.11.1.tgz", + "integrity": "sha1-6hDYEQN2mC/vV434/DC5rDCgej4=", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/resolve-cwd/download/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/resolve-from/download/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/resolve-from/download/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "http://registry.npm.taobao.org/resolve-url/download/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/restore-cursor/download/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "http://registry.npm.taobao.org/ret/download/ret-0.1.15.tgz", + "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", + "dev": true + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/rgb-regex/download/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", + "dev": true + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/rgba-regex/download/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "http://registry.npm.taobao.org/right-align/download/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "http://registry.npm.taobao.org/rimraf/download/rimraf-2.6.3.tgz", + "integrity": "sha1-stEE/g2Psnz54KHNqCYt04M8bKs=", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/ripemd160/download/ripemd160-2.0.2.tgz", + "integrity": "sha1-ocGm9iR1FXe6XQeRTLyShQWFiQw=", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "http://registry.npm.taobao.org/run-async/download/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "http://registry.npm.taobao.org/run-queue/download/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "http://registry.npm.taobao.org/rx-lite/download/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "http://registry.npm.taobao.org/rx-lite-aggregates/download/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "*" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz", + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/safe-regex/download/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "http://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz", + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "http://registry.npm.taobao.org/sax/download/sax-1.2.4.tgz", + "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=", + "dev": true + }, + "schema-utils": { + "version": "0.3.0", + "resolved": "http://registry.npm.taobao.org/schema-utils/download/schema-utils-0.3.0.tgz", + "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", + "dev": true, + "requires": { + "ajv": "^5.0.0" + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/select-hose/download/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selfsigned": { + "version": "1.10.4", + "resolved": "http://registry.npm.taobao.org/selfsigned/download/selfsigned-1.10.4.tgz", + "integrity": "sha1-zdfsz8pO12NdR6CL8tXTB0CS4s0=", + "dev": true, + "requires": { + "node-forge": "0.7.5" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npm.taobao.org/semver/download/semver-5.7.0.tgz", + "integrity": "sha1-eQp89v6lRZuslhELKbYEEtyP+Ws=", + "dev": true + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npm.taobao.org/send/download/send-0.17.1.tgz", + "integrity": "sha1-wdiwWfeQD3Rm3Uk4vcROEd2zdsg=", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.1.tgz", + "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "1.7.0", + "resolved": "https://registry.npm.taobao.org/serialize-javascript/download/serialize-javascript-1.7.0.tgz", + "integrity": "sha1-1uDfsqODKoyURo5usduX5VoZKmU=", + "dev": true + }, + "serve-index": { + "version": "1.9.1", + "resolved": "http://registry.npm.taobao.org/serve-index/download/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npm.taobao.org/http-errors/download/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz?cache=0&sync_timestamp=1560975547815&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.0.tgz", + "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", + "dev": true + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npm.taobao.org/serve-static/download/serve-static-1.14.1.tgz", + "integrity": "sha1-Zm5jbcTwEPfvKZcKiKZ0MgiYsvk=", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/set-blocking/download/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/set-value/download/set-value-2.0.1.tgz", + "integrity": "sha1-oY1AUw5vB95CKMfe/kInr4ytAFs=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "http://registry.npm.taobao.org/setimmediate/download/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.1.tgz", + "integrity": "sha1-fpWsskqpL1iF4KvvW6ExMw1K5oM=", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "http://registry.npm.taobao.org/sha.js/download/sha.js-2.4.11.tgz", + "integrity": "sha1-N6XPC4HsvGlD3hCbopYNGyZYSuc=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/shebang-command/download/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/shebang-regex/download/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shelljs": { + "version": "0.7.8", + "resolved": "http://registry.npm.taobao.org/shelljs/download/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "shellwords": { + "version": "0.1.1", + "resolved": "http://registry.npm.taobao.org/shellwords/download/shellwords-0.1.1.tgz", + "integrity": "sha1-1rkYHBpI05cyTISHHvvPxz/AZUs=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "http://registry.npm.taobao.org/signal-exit/download/signal-exit-3.0.2.tgz?cache=0&other_urls=http%3A%2F%2Fregistry.npm.taobao.org%2Fsignal-exit%2Fdownload%2Fsignal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "http://registry.npm.taobao.org/simple-swizzle/download/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dev": true, + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "http://registry.npm.taobao.org/is-arrayish/download/is-arrayish-0.3.2.tgz", + "integrity": "sha1-RXSirlb3qyBolvtDHq7tBm/fjwM=", + "dev": true + } + } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/slash/download/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/slice-ansi/download/slice-ansi-1.0.0.tgz", + "integrity": "sha1-BE8aSdiEL/MHqta1Be0Xi9lQE00=", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "http://registry.npm.taobao.org/snapdragon/download/snapdragon-0.8.2.tgz", + "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/snapdragon-node/download/snapdragon-node-2.1.1.tgz", + "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-6.0.2.tgz", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "http://registry.npm.taobao.org/snapdragon-util/download/snapdragon-util-3.0.1.tgz", + "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + } + }, + "sockjs": { + "version": "0.3.19", + "resolved": "http://registry.npm.taobao.org/sockjs/download/sockjs-0.3.19.tgz", + "integrity": "sha1-2Xa76ACve9IK4IWY1YI5NQiZPA0=", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "sockjs-client": { + "version": "1.1.5", + "resolved": "http://registry.npm.taobao.org/sockjs-client/download/sockjs-client-1.1.5.tgz", + "integrity": "sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=", + "dev": true, + "requires": { + "debug": "^2.6.6", + "eventsource": "0.1.6", + "faye-websocket": "~0.11.0", + "inherits": "^2.0.1", + "json3": "^3.3.2", + "url-parse": "^1.1.8" + }, + "dependencies": { + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npm.taobao.org/faye-websocket/download/faye-websocket-0.11.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffaye-websocket%2Fdownload%2Ffaye-websocket-0.11.3.tgz", + "integrity": "sha1-XA6aiWjokSwoZjn96XeosgnyUI4=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + } + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/sort-keys/download/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "http://registry.npm.taobao.org/source-list-map/download/source-list-map-2.0.1.tgz", + "integrity": "sha1-OZO9hzv8SEecyp6jpUeDXHwVSzQ=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "http://registry.npm.taobao.org/source-map-resolve/download/source-map-resolve-0.5.2.tgz", + "integrity": "sha1-cuLMNAlVQ+Q7LGKyxMENSpBU8lk=", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "http://registry.npm.taobao.org/source-map-support/download/source-map-support-0.4.18.tgz", + "integrity": "sha1-Aoam3ovkJkEzhZTpfM6nXwosWF8=", + "dev": true, + "requires": { + "source-map": "^0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "http://registry.npm.taobao.org/source-map-url/download/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "http://registry.npm.taobao.org/spdx-correct/download/spdx-correct-3.1.0.tgz", + "integrity": "sha1-+4PlBERSaPFUsHTiGMh8ADzTHfQ=", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "http://registry.npm.taobao.org/spdx-exceptions/download/spdx-exceptions-2.2.0.tgz", + "integrity": "sha1-LqRQrudPKom/uUUZwH/Nb0EyKXc=", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/spdx-expression-parse/download/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha1-meEZt6XaAOBUkcn6M4t5BII7QdA=", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npm.taobao.org/spdx-license-ids/download/spdx-license-ids-3.0.5.tgz", + "integrity": "sha1-NpS1gEVnpFjTyARYQqY1hjL2JlQ=", + "dev": true + }, + "spdy": { + "version": "4.0.0", + "resolved": "http://registry.npm.taobao.org/spdy/download/spdy-4.0.0.tgz", + "integrity": "sha1-gfIitadDoymqEs6mo5DmDpthPFI=", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "http://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz", + "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + } + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/spdy-transport/download/spdy-transport-3.0.0.tgz", + "integrity": "sha1-ANSGOmQArXXfkzYaFghgXl3NzzE=", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "http://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz", + "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-3.4.0.tgz", + "integrity": "sha1-pRwmdUZY4KPCHb9ZFjvUW6b0R/w=", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/split-string/download/split-string-3.1.0.tgz", + "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "http://registry.npm.taobao.org/sprintf-js/download/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "ssri": { + "version": "5.3.0", + "resolved": "http://registry.npm.taobao.org/ssri/download/ssri-5.3.0.tgz", + "integrity": "sha1-ujhyycbTOgcEp9cf8EXl7EiZnQY=", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "http://registry.npm.taobao.org/stable/download/stable-0.1.8.tgz", + "integrity": "sha1-g26zyDgv4pNv6vVEYxAXzn1Ho88=", + "dev": true + }, + "stackframe": { + "version": "1.0.4", + "resolved": "http://registry.npm.taobao.org/stackframe/download/stackframe-1.0.4.tgz", + "integrity": "sha1-NXskqZL5Qny6a1RdlqFO0svKGHs=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "http://registry.npm.taobao.org/static-extend/download/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "http://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "http://registry.npm.taobao.org/stream-browserify/download/stream-browserify-2.0.2.tgz", + "integrity": "sha1-h1IdOKRKp+6RzhzSpH3wy0ndZgs=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "http://registry.npm.taobao.org/stream-each/download/stream-each-1.2.3.tgz", + "integrity": "sha1-6+J6DDibBPvMIzZClS4Qcxr6m64=", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "http://registry.npm.taobao.org/stream-http/download/stream-http-2.8.3.tgz", + "integrity": "sha1-stJCRpKIpaJ+xP6JM6z2I95lFPw=", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/stream-shift/download/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/strict-uri-encode/download/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/string-width/download/string-width-2.1.1.tgz", + "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/strip-bom/download/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/strip-eof/download/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/strip-indent/download/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/strip-json-comments/download/strip-json-comments-2.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstrip-json-comments%2Fdownload%2Fstrip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "http://registry.npm.taobao.org/stylehacks/download/stylehacks-4.0.3.tgz", + "integrity": "sha1-Zxj8r00eB9ihMYaQiB6NlnJqcdU=", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "browserslist": { + "version": "4.6.6", + "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-4.6.6.tgz", + "integrity": "sha1-bkv0Z83lILydvfN0fa+gNTHOxFM=", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000984", + "electron-to-chromium": "^1.3.191", + "node-releases": "^1.1.25" + } + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-7.0.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.17.tgz", + "integrity": "sha1-TaG9/1Mi1KCsqrTYfz54JDa60x8=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "http://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-5.5.0.tgz", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "svgo": { + "version": "0.7.2", + "resolved": "https://registry.npm.taobao.org/svgo/download/svgo-0.7.2.tgz", + "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", + "dev": true, + "requires": { + "coa": "~1.0.1", + "colors": "~1.1.2", + "csso": "~2.3.1", + "js-yaml": "~3.7.0", + "mkdirp": "~0.5.1", + "sax": "~1.2.1", + "whet.extend": "~0.9.9" + } + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npm.taobao.org/table/download/table-4.0.2.tgz", + "integrity": "sha1-ozRHN1OR52atNNNIbm4q7chNLjY=", + "dev": true, + "requires": { + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } + }, + "tapable": { + "version": "0.2.9", + "resolved": "https://registry.npm.taobao.org/tapable/download/tapable-0.2.9.tgz", + "integrity": "sha1-ry2LvJsE907hevK02QSPgHrNGKg=", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "http://registry.npm.taobao.org/text-table/download/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "http://registry.npm.taobao.org/through/download/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "http://registry.npm.taobao.org/through2/download/through2-2.0.5.tgz", + "integrity": "sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.0.3", + "resolved": "http://registry.npm.taobao.org/thunky/download/thunky-1.0.3.tgz", + "integrity": "sha1-9d9zJFNAewkZHa5z4qjMc/OBqCY=", + "dev": true + }, + "time-stamp": { + "version": "2.2.0", + "resolved": "http://registry.npm.taobao.org/time-stamp/download/time-stamp-2.2.0.tgz", + "integrity": "sha1-kX4KZpBWiHkOx7u94EBGJZr4P1c=", + "dev": true + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "http://registry.npm.taobao.org/timers-browserify/download/timers-browserify-2.0.10.tgz", + "integrity": "sha1-HSjj0qrfHVpZlsTp+VYBzQU0gK4=", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "http://registry.npm.taobao.org/timsort/download/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "http://registry.npm.taobao.org/tmp/download/tmp-0.0.33.tgz", + "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/to-arraybuffer/download/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "http://registry.npm.taobao.org/to-fast-properties/download/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "http://registry.npm.taobao.org/to-object-path/download/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "http://registry.npm.taobao.org/to-regex/download/to-regex-3.0.2.tgz", + "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/to-regex-range/download/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/toidentifier/download/toidentifier-1.0.0.tgz", + "integrity": "sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM=", + "dev": true + }, + "toposort": { + "version": "1.0.7", + "resolved": "http://registry.npm.taobao.org/toposort/download/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/trim-newlines/download/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/trim-right/download/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tryer": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/tryer/download/tryer-1.0.1.tgz", + "integrity": "sha1-8shUBoALmw90yfdGW4HqrSQSUvg=", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "http://registry.npm.taobao.org/tty-browserify/download/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "type": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/type/download/type-1.0.1.tgz", + "integrity": "sha1-CEyaF/zJFRos2xRZkFwuReS7fWE=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "http://registry.npm.taobao.org/type-check/download/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npm.taobao.org/type-is/download/type-is-1.6.18.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftype-is%2Fdownload%2Ftype-is-1.6.18.tgz", + "integrity": "sha1-TlUs0F3wlGfcvE73Od6J8s83wTE=", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "http://registry.npm.taobao.org/typedarray/download/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npm.taobao.org/uglify-js/download/uglify-js-3.4.10.tgz", + "integrity": "sha1-mtlWPY6zrN+404WX0q8dgV9qdV8=", + "dev": true, + "requires": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "http://registry.npm.taobao.org/commander/download/commander-2.19.0.tgz", + "integrity": "sha1-9hmKqE5bg8RgVLlN3tv+1e6f8So=", + "dev": true + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/uglify-to-browserify/download/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "uglifyjs-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npm.taobao.org/uglifyjs-webpack-plugin/download/uglifyjs-webpack-plugin-1.3.0.tgz", + "integrity": "sha1-dfVIFghYFjoIZD4IbV/v4YpdZ94=", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.10.2.tgz?cache=0&sync_timestamp=1563141444360&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.10.2.tgz", + "integrity": "sha1-086gTWsBeyiUrWkED+yLYj60vVI=", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npm.taobao.org/ajv-keywords/download/ajv-keywords-3.4.1.tgz", + "integrity": "sha1-75FuJxxkrBIXH9g4TqrmsjRYVNo=", + "dev": true + }, + "commander": { + "version": "2.13.0", + "resolved": "http://registry.npm.taobao.org/commander/download/commander-2.13.0.tgz", + "integrity": "sha1-aWS8pnaF33wfFDDFhPB9dZeIW5w=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-2.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffast-deep-equal%2Fdownload%2Ffast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "http://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "dev": true + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "http://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz", + "integrity": "sha1-unT1l9K+LqiAExdG7hfQoJPGgYc=", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + }, + "uglify-es": { + "version": "3.3.9", + "resolved": "http://registry.npm.taobao.org/uglify-es/download/uglify-es-3.3.9.tgz", + "integrity": "sha1-DBxPBwC+2NvBJM2zBNJZLKID5nc=", + "dev": true, + "requires": { + "commander": "~2.13.0", + "source-map": "~0.6.1" + } + } + } + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/union-value/download/union-value-1.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Funion-value%2Fdownload%2Funion-value-1.0.1.tgz", + "integrity": "sha1-C2/nuDWuzaYcbqTU8CwUIh4QmEc=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/uniq/download/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "uniqs": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/uniqs/download/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/unique-filename/download/unique-filename-1.1.1.tgz", + "integrity": "sha1-HWl2k2mtoFgxA6HmrodoG1ZXMjA=", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npm.taobao.org/unique-slug/download/unique-slug-2.0.2.tgz", + "integrity": "sha1-uqvOkQg/xk6UWw861hPiZPfNTmw=", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unquote": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/unquote/download/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/unset-value/download/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "http://registry.npm.taobao.org/has-value/download/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/isobject/download/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "http://registry.npm.taobao.org/has-values/download/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.2", + "resolved": "http://registry.npm.taobao.org/upath/download/upath-1.1.2.tgz", + "integrity": "sha1-PbZYYA7a7sy+bbXmhNZ+6MKs0Gg=", + "dev": true + }, + "upper-case": { + "version": "1.1.3", + "resolved": "http://registry.npm.taobao.org/upper-case/download/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "http://registry.npm.taobao.org/uri-js/download/uri-js-4.2.2.tgz", + "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "http://registry.npm.taobao.org/urix/download/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "http://registry.npm.taobao.org/url/download/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "http://registry.npm.taobao.org/punycode/download/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-loader": { + "version": "0.5.9", + "resolved": "https://registry.npm.taobao.org/url-loader/download/url-loader-0.5.9.tgz", + "integrity": "sha1-zI/qgse5Bud3cBklCGnlaemVwpU=", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "mime": "1.3.x" + }, + "dependencies": { + "mime": { + "version": "1.3.6", + "resolved": "https://registry.npm.taobao.org/mime/download/mime-1.3.6.tgz?cache=0&sync_timestamp=1560034758817&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime%2Fdownload%2Fmime-1.3.6.tgz", + "integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA=", + "dev": true + } + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npm.taobao.org/url-parse/download/url-parse-1.4.7.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Furl-parse%2Fdownload%2Furl-parse-1.4.7.tgz", + "integrity": "sha1-qKg1NejACjFuQDpdtKwbm4U64ng=", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "http://registry.npm.taobao.org/use/download/use-3.1.1.tgz", + "integrity": "sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8=", + "dev": true + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npm.taobao.org/util/download/util-0.11.1.tgz", + "integrity": "sha1-MjZzNyDsZLsn9uJvQhqqLhtYjWE=", + "dev": true, + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz?cache=0&sync_timestamp=1560975547815&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/util.promisify/download/util.promisify-1.0.0.tgz", + "integrity": "sha1-RA9xZaRZyaFtwUXrjnLzVocJcDA=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utila": { + "version": "0.4.0", + "resolved": "http://registry.npm.taobao.org/utila/download/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "http://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "http://registry.npm.taobao.org/uuid/download/uuid-3.3.2.tgz", + "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "http://registry.npm.taobao.org/validate-npm-package-license/download/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha1-/JH2uce6FchX9MssXe/uw51PQQo=", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "http://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "vendors": { + "version": "1.0.3", + "resolved": "https://registry.npm.taobao.org/vendors/download/vendors-1.0.3.tgz", + "integrity": "sha1-pkZ3gavTZiF8BQ+CAuflDMnu+MA=", + "dev": true + }, + "vm-browserify": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/vm-browserify/download/vm-browserify-1.1.0.tgz", + "integrity": "sha1-vXbWojMj4sqP+hICjcBFWcdfkBk=", + "dev": true + }, + "vue": { + "version": "2.6.10", + "resolved": "http://registry.npm.taobao.org/vue/download/vue-2.6.10.tgz", + "integrity": "sha1-pysaQqTYKnIepDjRtr9V5mGVxjc=" + }, + "vue-eslint-parser": { + "version": "2.0.3", + "resolved": "https://registry.npm.taobao.org/vue-eslint-parser/download/vue-eslint-parser-2.0.3.tgz", + "integrity": "sha1-wmjJbG2Uz+PZOKX3WTlZsMozYNE=", + "dev": true, + "requires": { + "debug": "^3.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.2", + "esquery": "^1.0.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "http://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + } + } + }, + "vue-hot-reload-api": { + "version": "2.3.3", + "resolved": "http://registry.npm.taobao.org/vue-hot-reload-api/download/vue-hot-reload-api-2.3.3.tgz", + "integrity": "sha1-J1b0bLMlgFTF9HI96K5+hzAqHM8=", + "dev": true + }, + "vue-loader": { + "version": "13.7.3", + "resolved": "http://registry.npm.taobao.org/vue-loader/download/vue-loader-13.7.3.tgz", + "integrity": "sha1-4HRA94IwpjnQCtpNp7ltDp1iA38=", + "dev": true, + "requires": { + "consolidate": "^0.14.0", + "hash-sum": "^1.0.2", + "loader-utils": "^1.1.0", + "lru-cache": "^4.1.1", + "postcss": "^6.0.8", + "postcss-load-config": "^1.1.0", + "postcss-selector-parser": "^2.0.0", + "prettier": "^1.7.0", + "resolve": "^1.4.0", + "source-map": "^0.6.1", + "vue-hot-reload-api": "^2.2.0", + "vue-style-loader": "^3.0.0", + "vue-template-es2015-compiler": "^1.6.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "2.2.2", + "resolved": "https://registry.npm.taobao.org/cosmiconfig/download/cosmiconfig-2.2.2.tgz", + "integrity": "sha1-YXPOvVb6wELB9DkO33r2wHx8uJI=", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.4.3", + "minimist": "^1.2.0", + "object-assign": "^4.1.0", + "os-homedir": "^1.0.1", + "parse-json": "^2.2.0", + "require-from-string": "^1.1.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "postcss-load-config": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/postcss-load-config/download/postcss-load-config-1.2.0.tgz", + "integrity": "sha1-U56a/J3chiASHr+djDZz4M5Q0oo=", + "dev": true, + "requires": { + "cosmiconfig": "^2.1.0", + "object-assign": "^4.1.0", + "postcss-load-options": "^1.2.0", + "postcss-load-plugins": "^2.3.0" + } + } + } + }, + "vue-router": { + "version": "3.0.7", + "resolved": "https://registry.npm.taobao.org/vue-router/download/vue-router-3.0.7.tgz?cache=0&sync_timestamp=1562164292099&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-router%2Fdownload%2Fvue-router-3.0.7.tgz", + "integrity": "sha1-s2yhB7SsuP9bxP+CRYQFnCP8uHs=" + }, + "vue-style-loader": { + "version": "3.1.2", + "resolved": "http://registry.npm.taobao.org/vue-style-loader/download/vue-style-loader-3.1.2.tgz", + "integrity": "sha1-a2atNJmPyVIMLx5NX6QJFkHBWXo=", + "dev": true, + "requires": { + "hash-sum": "^1.0.2", + "loader-utils": "^1.0.2" + } + }, + "vue-template-compiler": { + "version": "2.6.10", + "resolved": "http://registry.npm.taobao.org/vue-template-compiler/download/vue-template-compiler-2.6.10.tgz", + "integrity": "sha1-MjtPNJXwT6o1AzN6gvXWUHeZycw=", + "dev": true, + "requires": { + "de-indent": "^1.0.2", + "he": "^1.1.0" + } + }, + "vue-template-es2015-compiler": { + "version": "1.9.1", + "resolved": "http://registry.npm.taobao.org/vue-template-es2015-compiler/download/vue-template-es2015-compiler-1.9.1.tgz", + "integrity": "sha1-HuO8mhbsv1EYvjNLsV+cRvgvWCU=", + "dev": true + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npm.taobao.org/watchpack/download/watchpack-1.6.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwatchpack%2Fdownload%2Fwatchpack-1.6.0.tgz", + "integrity": "sha1-S8EsLr6KonenHx0/FNaFx7RGzQA=", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "http://registry.npm.taobao.org/wbuf/download/wbuf-1.7.3.tgz", + "integrity": "sha1-wdjRSTFtPqhShIiVy2oL/oh7h98=", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "webpack": { + "version": "3.12.0", + "resolved": "https://registry.npm.taobao.org/webpack/download/webpack-3.12.0.tgz?cache=0&sync_timestamp=1562617992813&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwebpack%2Fdownload%2Fwebpack-3.12.0.tgz", + "integrity": "sha1-P540NgNwYC/PY56Xk520hvTsDXQ=", + "dev": true, + "requires": { + "acorn": "^5.0.0", + "acorn-dynamic-import": "^2.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "async": "^2.1.2", + "enhanced-resolve": "^3.4.0", + "escope": "^3.6.0", + "interpret": "^1.0.0", + "json-loader": "^0.5.4", + "json5": "^0.5.1", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "mkdirp": "~0.5.0", + "node-libs-browser": "^2.0.0", + "source-map": "^0.5.3", + "supports-color": "^4.2.1", + "tapable": "^0.2.7", + "uglifyjs-webpack-plugin": "^0.4.6", + "watchpack": "^1.4.0", + "webpack-sources": "^1.0.1", + "yargs": "^8.0.2" + }, + "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.10.2.tgz?cache=0&sync_timestamp=1563141444360&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.10.2.tgz", + "integrity": "sha1-086gTWsBeyiUrWkED+yLYj60vVI=", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npm.taobao.org/ajv-keywords/download/ajv-keywords-3.4.1.tgz", + "integrity": "sha1-75FuJxxkrBIXH9g4TqrmsjRYVNo=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-2.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffast-deep-equal%2Fdownload%2Ffast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "has-flag": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "http://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "^2.0.0" + } + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npm.taobao.org/uglify-js/download/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npm.taobao.org/yargs/download/yargs-3.10.0.tgz?cache=0&sync_timestamp=1560347597423&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs%2Fdownload%2Fyargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "uglifyjs-webpack-plugin": { + "version": "0.4.6", + "resolved": "https://registry.npm.taobao.org/uglifyjs-webpack-plugin/download/uglifyjs-webpack-plugin-0.4.6.tgz", + "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", + "dev": true, + "requires": { + "source-map": "^0.5.6", + "uglify-js": "^2.8.29", + "webpack-sources": "^1.0.1" + } + } + } + }, + "webpack-bundle-analyzer": { + "version": "2.13.1", + "resolved": "https://registry.npm.taobao.org/webpack-bundle-analyzer/download/webpack-bundle-analyzer-2.13.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwebpack-bundle-analyzer%2Fdownload%2Fwebpack-bundle-analyzer-2.13.1.tgz", + "integrity": "sha1-B9IXbG6Gw83OTCPlb64qe2tK1SY=", + "dev": true, + "requires": { + "acorn": "^5.3.0", + "bfj-node4": "^5.2.0", + "chalk": "^2.3.0", + "commander": "^2.13.0", + "ejs": "^2.5.7", + "express": "^4.16.2", + "filesize": "^3.5.11", + "gzip-size": "^4.1.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "opener": "^1.4.3", + "ws": "^4.0.0" + } + }, + "webpack-dev-middleware": { + "version": "1.12.2", + "resolved": "https://registry.npm.taobao.org/webpack-dev-middleware/download/webpack-dev-middleware-1.12.2.tgz", + "integrity": "sha1-+PwRIM47T8VoDO7LQ9d3lmshEF4=", + "dev": true, + "requires": { + "memory-fs": "~0.4.1", + "mime": "^1.5.0", + "path-is-absolute": "^1.0.0", + "range-parser": "^1.0.3", + "time-stamp": "^2.0.0" + } + }, + "webpack-dev-server": { + "version": "2.11.5", + "resolved": "https://registry.npm.taobao.org/webpack-dev-server/download/webpack-dev-server-2.11.5.tgz?cache=0&sync_timestamp=1560844041188&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwebpack-dev-server%2Fdownload%2Fwebpack-dev-server-2.11.5.tgz", + "integrity": "sha1-QW+96g4E7r5EpibnkdWi6zf+jEg=", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "array-includes": "^3.0.3", + "bonjour": "^3.5.0", + "chokidar": "^2.1.2", + "compression": "^1.7.3", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "^0.19.1", + "import-local": "^1.0.0", + "internal-ip": "1.2.0", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "selfsigned": "^1.9.1", + "serve-index": "^1.9.1", + "sockjs": "0.3.19", + "sockjs-client": "1.1.5", + "spdy": "^4.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", + "webpack-dev-middleware": "1.12.2", + "yargs": "6.6.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "http://registry.npm.taobao.org/camelcase/download/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "http://registry.npm.taobao.org/cliui/download/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "http://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/find-up/download/find-up-1.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-up%2Fdownload%2Ffind-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/load-json-file/download/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npm.taobao.org/os-locale/download/os-locale-1.4.0.tgz?cache=0&sync_timestamp=1560274285880&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fos-locale%2Fdownload%2Fos-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "http://registry.npm.taobao.org/path-exists/download/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "http://registry.npm.taobao.org/path-type/download/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/read-pkg/download/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/strip-bom/download/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/which-module/download/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "http://registry.npm.taobao.org/y18n/download/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "6.6.0", + "resolved": "https://registry.npm.taobao.org/yargs/download/yargs-6.6.0.tgz?cache=0&sync_timestamp=1560347597423&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs%2Fdownload%2Fyargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^4.2.0" + } + }, + "yargs-parser": { + "version": "4.2.1", + "resolved": "https://registry.npm.taobao.org/yargs-parser/download/yargs-parser-4.2.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs-parser%2Fdownload%2Fyargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + } + } + } + }, + "webpack-merge": { + "version": "4.2.1", + "resolved": "http://registry.npm.taobao.org/webpack-merge/download/webpack-merge-4.2.1.tgz", + "integrity": "sha1-XpI8+ALqKs5P1a8dMkc2imM0ibQ=", + "dev": true, + "requires": { + "lodash": "^4.17.5" + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "http://registry.npm.taobao.org/webpack-sources/download/webpack-sources-1.3.0.tgz", + "integrity": "sha1-KijcufH0X+lg2PFJMlK17mUw+oU=", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "websocket-driver": { + "version": "0.7.3", + "resolved": "https://registry.npm.taobao.org/websocket-driver/download/websocket-driver-0.7.3.tgz", + "integrity": "sha1-otTg1PTxFvHmKX66WLBdQwEA6fk=", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0 <0.4.11", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "http://registry.npm.taobao.org/websocket-extensions/download/websocket-extensions-0.1.3.tgz", + "integrity": "sha1-XS/yKXcAPsaHpLhwc9+7rBRszyk=", + "dev": true + }, + "whet.extend": { + "version": "0.9.9", + "resolved": "http://registry.npm.taobao.org/whet.extend/download/whet.extend-0.9.9.tgz", + "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "http://registry.npm.taobao.org/which/download/which-1.3.1.tgz", + "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/which-module/download/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "http://registry.npm.taobao.org/window-size/download/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/wordwrap/download/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npm.taobao.org/worker-farm/download/worker-farm-1.7.0.tgz", + "integrity": "sha1-JqlMU5G7ypJhUgAvabhKS/dy5ag=", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/wrappy/download/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "http://registry.npm.taobao.org/write/download/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "ws": { + "version": "4.1.0", + "resolved": "https://registry.npm.taobao.org/ws/download/ws-4.1.0.tgz?cache=0&sync_timestamp=1562602325578&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fws%2Fdownload%2Fws-4.1.0.tgz", + "integrity": "sha1-qXm119TaaL9U7+BAiWfDJIaacok=", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npm.taobao.org/xtend/download/xtend-4.0.2.tgz", + "integrity": "sha1-u3J3n1+kZRhrH0OPZ0+jR/2121Q=", + "dev": true + }, + "xxhashjs": { + "version": "0.2.2", + "resolved": "http://registry.npm.taobao.org/xxhashjs/download/xxhashjs-0.2.2.tgz", + "integrity": "sha1-imJRVnYhocRqWuIE2gJJx/jKqdg=", + "dev": true, + "requires": { + "cuint": "^0.2.2" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "http://registry.npm.taobao.org/y18n/download/y18n-4.0.0.tgz", + "integrity": "sha1-le+U+F7MgdAHwmThkKEg8KPIVms=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "http://registry.npm.taobao.org/yallist/download/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "8.0.2", + "resolved": "https://registry.npm.taobao.org/yargs/download/yargs-8.0.2.tgz?cache=0&sync_timestamp=1560347597423&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs%2Fdownload%2Fyargs-8.0.2.tgz", + "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "http://registry.npm.taobao.org/camelcase/download/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "http://registry.npm.taobao.org/cliui/download/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "y18n": { + "version": "3.2.1", + "resolved": "http://registry.npm.taobao.org/y18n/download/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + } + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npm.taobao.org/yargs-parser/download/yargs-parser-7.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs-parser%2Fdownload%2Fyargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "http://registry.npm.taobao.org/camelcase/download/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..48bbc93 --- /dev/null +++ b/web/package.json @@ -0,0 +1,81 @@ +{ + "name": "longbing_back", + "version": "1.0.0", + "description": "A Vue.js project", + "author": "akang <815101207@qq.com>", + "private": true, + "scripts": { + "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", + "start": "npm run dev", + "lint": "eslint --ext .js,.vue src", + "build": "node build/build.js" + }, + "dependencies": { + "axios": "^0.19.0", + "element-ui": "^2.10.1", + "moment": "^2.24.0", + "vue": "^2.5.2", + "vue-i18n": "^8.12.0", + "vue-router": "^3.0.1", + "vuex": "^3.1.1" + }, + "devDependencies": { + "autoprefixer": "^7.1.2", + "babel-core": "^6.22.1", + "babel-eslint": "^8.2.1", + "babel-helper-vue-jsx-merge-props": "^2.0.3", + "babel-loader": "^7.1.1", + "babel-plugin-syntax-jsx": "^6.18.0", + "babel-plugin-transform-runtime": "^6.22.0", + "babel-plugin-transform-vue-jsx": "^3.5.0", + "babel-preset-env": "^1.3.2", + "babel-preset-stage-2": "^6.22.0", + "chalk": "^2.0.1", + "copy-webpack-plugin": "^4.0.1", + "css-loader": "^0.28.0", + "eslint": "^4.15.0", + "eslint-config-standard": "^10.2.1", + "eslint-friendly-formatter": "^3.0.0", + "eslint-loader": "^1.7.1", + "eslint-plugin-import": "^2.7.0", + "eslint-plugin-node": "^5.2.0", + "eslint-plugin-promise": "^3.4.0", + "eslint-plugin-standard": "^3.0.1", + "eslint-plugin-vue": "^4.0.0", + "extract-text-webpack-plugin": "^3.0.0", + "file-loader": "^1.1.4", + "friendly-errors-webpack-plugin": "^1.6.1", + "html-webpack-plugin": "^2.30.1", + "node-notifier": "^5.1.2", + "node-sass": "^4.12.0", + "optimize-css-assets-webpack-plugin": "^3.2.0", + "ora": "^1.2.0", + "portfinder": "^1.0.13", + "postcss-import": "^11.0.0", + "postcss-loader": "^2.0.8", + "postcss-url": "^7.2.1", + "rimraf": "^2.6.0", + "sass-loader": "^7.1.0", + "sass-resources-loader": "^2.0.1", + "semver": "^5.3.0", + "shelljs": "^0.7.6", + "uglifyjs-webpack-plugin": "^1.1.1", + "url-loader": "^0.5.8", + "vue-loader": "^13.3.0", + "vue-style-loader": "^3.0.1", + "vue-template-compiler": "^2.5.2", + "webpack": "^3.6.0", + "webpack-bundle-analyzer": "^2.9.0", + "webpack-dev-server": "^2.9.1", + "webpack-merge": "^4.1.0" + }, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not ie <= 8" + ] +} diff --git a/web/runtime/services.php b/web/runtime/services.php new file mode 100644 index 0000000..53a50c8 --- /dev/null +++ b/web/runtime/services.php @@ -0,0 +1,6 @@ + 'think\\migration\\Service', +); \ No newline at end of file diff --git a/web/src/App.vue b/web/src/App.vue new file mode 100644 index 0000000..2a33b5a --- /dev/null +++ b/web/src/App.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/web/src/api/index.js b/web/src/api/index.js new file mode 100644 index 0000000..6e59ba2 --- /dev/null +++ b/web/src/api/index.js @@ -0,0 +1,127 @@ +import axios from 'axios' +import qs from 'qs' +// import router from '../router' +// api 模块化 +import apis from './modules' + +axios.defaults.timeout = 10000 +axios.defaults.baseURL = process.env.NODE_ENV === 'development' ? 'http://47.100.26.121:85' : '' +axios.interceptors.request.use(config => { + if (localStorage.getItem('token')) { + config.headers = { + 'Authorization': localStorage.getItem('token'), + 'Content-Type': 'application/x-www-form-urlencoded' + } + } + return config +}, error => { + return Promise.reject(error) +}) +axios.interceptors.response.use(res => { +// if (res.data.code === '401') { // token验证不合法或者没有 +// localStorage.removeItem('token') // 删除token +// localStorage.removeItem('ms_username') // 删除用户名 +// router.push('/login') +// } + return res +}, err => { + return Promise.resolve(err.response) +}) + +/** + * 封装get方法 + * @param url + * @param data + * @returns {Promise} + */ + +export function get (url, params = {}) { + return new Promise((resolve, reject) => { + axios.get(url, { + params: params + }) + .then(response => { + resolve(response.data) + }) + .catch(err => { + reject(err) + }) + }) +} + +/** + * 封装post请求 + * @param url + * @param data + * @returns {Promise} + */ + +export function post (url, data = {}) { + return new Promise((resolve, reject) => { + axios.post(url, qs.stringify(data)) + .then(response => { + resolve(response.data) + }, err => { + reject(err) + }) + }) +} + +/** + * 封装patch请求 + * @param url + * @param data + * @returns {Promise} + */ + +export function patch (url, data = {}) { + return new Promise((resolve, reject) => { + axios.patch(url, data) + .then(response => { + resolve(response.data) + }, err => { + reject(err) + }) + }) +} + +/** + * 封装put请求 + * @param url + * @param data + * @returns {Promise} + */ + +export function put (url, data = {}) { + return new Promise((resolve, reject) => { + axios.put(url, data) + .then(response => { + resolve(response.data) + }, err => { + reject(err) + }) + }) +} +/** + * @method 获取七牛token + * @param url + * @param data + * @returns {Promise} + */ +export function getQiniuToken (url, data = {}) { + return new Promise((resolve, reject) => { + axios.get(url, data) + .then(response => { + resolve(response.data) + }, err => { + reject(err) + }) + }) +} + +/** +* 获取数据的接口 +*/ +export const api = { + ...apis +} diff --git a/web/src/api/modules/businessCard.js b/web/src/api/modules/businessCard.js new file mode 100644 index 0000000..0287924 --- /dev/null +++ b/web/src/api/modules/businessCard.js @@ -0,0 +1 @@ +// import {get, post} from '../index' diff --git a/web/src/api/modules/index.js b/web/src/api/modules/index.js new file mode 100644 index 0000000..021b633 --- /dev/null +++ b/web/src/api/modules/index.js @@ -0,0 +1,5 @@ +import survey from './survey' + +export default { + ...survey +} diff --git a/web/src/api/modules/survey.js b/web/src/api/modules/survey.js new file mode 100644 index 0000000..8ee4fba --- /dev/null +++ b/web/src/api/modules/survey.js @@ -0,0 +1,7 @@ +import { get } from '../index' + +export default { + getRoutes () { + return get('/admin/test') + } +} diff --git a/web/src/assets/logo.png b/web/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d2503fc2a44b5053b0837ebea6e87a2d339a43 GIT binary patch literal 6849 zcmaKRcUV(fvo}bjDT-7nLI_nlK}sT_69H+`qzVWDA|yaU?}j417wLi^B1KB1SLsC& zL0ag7$U(XW5YR7p&Ux?sP$d4lvMt8C^+TcQu4F zQqv!UF!I+kw)c0jhd6+g6oCr9P?7)?!qX1ui*iL{p}sKCAGuJ{{W)0z1pLF|=>h}& zt(2Lr0Z`2ig8<5i%Zk}cO5Fm=LByqGWaS`oqChZdEFmc`0hSb#gg|Aap^{+WKOYcj zHjINK)KDG%&s?Mt4CL(T=?;~U@bU2x_mLKN!#GJuK_CzbNw5SMEJorG!}_5;?R>@1 zSl)jns3WlU7^J%=(hUtfmuUCU&C3%8B5C^f5>W2Cy8jW3#{Od{lF1}|?c61##3dzA zsPlFG;l_FzBK}8>|H_Ru_H#!_7$UH4UKo3lKOA}g1(R&|e@}GINYVzX?q=_WLZCgh z)L|eJMce`D0EIwgRaNETDsr+?vQknSGAi=7H00r`QnI%oQnFxm`G2umXso9l+8*&Q z7WqF|$p49js$mdzo^BXpH#gURy=UO;=IMrYc5?@+sR4y_?d*~0^YP7d+y0{}0)zBM zIKVM(DBvICK#~7N0a+PY6)7;u=dutmNqK3AlsrUU9U`d;msiucB_|8|2kY=(7XA;G zwDA8AR)VCA#JOkxm#6oHNS^YVuOU;8p$N)2{`;oF|rQ?B~K$%rHDxXs+_G zF5|-uqHZvSzq}L;5Kcy_P+x0${33}Ofb6+TX&=y;;PkEOpz%+_bCw_{<&~ zeLV|!bP%l1qxywfVr9Z9JI+++EO^x>ZuCK);=$VIG1`kxK8F2M8AdC$iOe3cj1fo(ce4l-9 z7*zKy3={MixvUk=enQE;ED~7tv%qh&3lR<0m??@w{ILF|e#QOyPkFYK!&Up7xWNtL zOW%1QMC<3o;G9_S1;NkPB6bqbCOjeztEc6TsBM<(q9((JKiH{01+Ud=uw9B@{;(JJ z-DxI2*{pMq`q1RQc;V8@gYAY44Z!%#W~M9pRxI(R?SJ7sy7em=Z5DbuDlr@*q|25V)($-f}9c#?D%dU^RS<(wz?{P zFFHtCab*!rl(~j@0(Nadvwg8q|4!}L^>d?0al6}Rrv9$0M#^&@zjbfJy_n!%mVHK4 z6pLRIQ^Uq~dnyy$`ay51Us6WaP%&O;@49m&{G3z7xV3dLtt1VTOMYl3UW~Rm{Eq4m zF?Zl_v;?7EFx1_+#WFUXxcK78IV)FO>42@cm@}2I%pVbZqQ}3;p;sDIm&knay03a^ zn$5}Q$G!@fTwD$e(x-~aWP0h+4NRz$KlnO_H2c< z(XX#lPuW_%H#Q+c&(nRyX1-IadKR-%$4FYC0fsCmL9ky3 zKpxyjd^JFR+vg2!=HWf}2Z?@Td`0EG`kU?{8zKrvtsm)|7>pPk9nu@2^z96aU2<#` z2QhvH5w&V;wER?mopu+nqu*n8p~(%QkwSs&*0eJwa zMXR05`OSFpfyRb!Y_+H@O%Y z0=K^y6B8Gcbl?SA)qMP3Z+=C(?8zL@=74R=EVnE?vY!1BQy2@q*RUgRx4yJ$k}MnL zs!?74QciNb-LcG*&o<9=DSL>1n}ZNd)w1z3-0Pd^4ED1{qd=9|!!N?xnXjM!EuylY z5=!H>&hSofh8V?Jofyd!h`xDI1fYAuV(sZwwN~{$a}MX^=+0TH*SFp$vyxmUv7C*W zv^3Gl0+eTFgBi3FVD;$nhcp)ka*4gSskYIqQ&+M}xP9yLAkWzBI^I%zR^l1e?bW_6 zIn{mo{dD=)9@V?s^fa55jh78rP*Ze<3`tRCN4*mpO$@7a^*2B*7N_|A(Ve2VB|)_o z$=#_=aBkhe(ifX}MLT()@5?OV+~7cXC3r!%{QJxriXo9I%*3q4KT4Xxzyd{ z9;_%=W%q!Vw$Z7F3lUnY+1HZ*lO;4;VR2+i4+D(m#01OYq|L_fbnT;KN<^dkkCwtd zF7n+O7KvAw8c`JUh6LmeIrk4`F3o|AagKSMK3))_5Cv~y2Bb2!Ibg9BO7Vkz?pAYX zoI=B}+$R22&IL`NCYUYjrdhwjnMx_v=-Qcx-jmtN>!Zqf|n1^SWrHy zK|MwJ?Z#^>)rfT5YSY{qjZ&`Fjd;^vv&gF-Yj6$9-Dy$<6zeP4s+78gS2|t%Z309b z0^fp~ue_}i`U9j!<|qF92_3oB09NqgAoehQ`)<)dSfKoJl_A6Ec#*Mx9Cpd-p#$Ez z={AM*r-bQs6*z$!*VA4|QE7bf@-4vb?Q+pPKLkY2{yKsw{&udv_2v8{Dbd zm~8VAv!G~s)`O3|Q6vFUV%8%+?ZSVUa(;fhPNg#vab@J*9XE4#D%)$UU-T5`fwjz! z6&gA^`OGu6aUk{l*h9eB?opVdrHK>Q@U>&JQ_2pR%}TyOXGq_6s56_`U(WoOaAb+K zXQr#6H}>a-GYs9^bGP2Y&hSP5gEtW+GVC4=wy0wQk=~%CSXj=GH6q z-T#s!BV`xZVxm{~jr_ezYRpqqIcXC=Oq`b{lu`Rt(IYr4B91hhVC?yg{ol4WUr3v9 zOAk2LG>CIECZ-WIs0$N}F#eoIUEtZudc7DPYIjzGqDLWk_A4#(LgacooD z2K4IWs@N`Bddm-{%oy}!k0^i6Yh)uJ1S*90>|bm3TOZxcV|ywHUb(+CeX-o1|LTZM zwU>dY3R&U)T(}5#Neh?-CWT~@{6Ke@sI)uSuzoah8COy)w)B)aslJmp`WUcjdia-0 zl2Y}&L~XfA`uYQboAJ1;J{XLhYjH){cObH3FDva+^8ioOQy%Z=xyjGLmWMrzfFoH; zEi3AG`_v+%)&lDJE;iJWJDI@-X9K5O)LD~j*PBe(wu+|%ar~C+LK1+-+lK=t# z+Xc+J7qp~5q=B~rD!x78)?1+KUIbYr^5rcl&tB-cTtj+e%{gpZZ4G~6r15+d|J(ky zjg@@UzMW0k9@S#W(1H{u;Nq(7llJbq;;4t$awM;l&(2s+$l!Ay9^Ge|34CVhr7|BG z?dAR83smef^frq9V(OH+a+ki#q&-7TkWfFM=5bsGbU(8mC;>QTCWL5ydz9s6k@?+V zcjiH`VI=59P-(-DWXZ~5DH>B^_H~;4$)KUhnmGo*G!Tq8^LjfUDO)lASN*=#AY_yS zqW9UX(VOCO&p@kHdUUgsBO0KhXxn1sprK5h8}+>IhX(nSXZKwlNsjk^M|RAaqmCZB zHBolOHYBas@&{PT=R+?d8pZu zUHfyucQ`(umXSW7o?HQ3H21M`ZJal+%*)SH1B1j6rxTlG3hx1IGJN^M7{$j(9V;MZ zRKybgVuxKo#XVM+?*yTy{W+XHaU5Jbt-UG33x{u(N-2wmw;zzPH&4DE103HV@ER86 z|FZEmQb|&1s5#`$4!Cm}&`^{(4V}OP$bk`}v6q6rm;P!H)W|2i^e{7lTk2W@jo_9q z*aw|U7#+g59Fv(5qI`#O-qPj#@_P>PC#I(GSp3DLv7x-dmYK=C7lPF8a)bxb=@)B1 zUZ`EqpXV2dR}B&r`uM}N(TS99ZT0UB%IN|0H%DcVO#T%L_chrgn#m6%x4KE*IMfjX zJ%4veCEqbXZ`H`F_+fELMC@wuy_ch%t*+Z+1I}wN#C+dRrf2X{1C8=yZ_%Pt6wL_~ zZ2NN-hXOT4P4n$QFO7yYHS-4wF1Xfr-meG9Pn;uK51?hfel`d38k{W)F*|gJLT2#T z<~>spMu4(mul-8Q3*pf=N4DcI)zzjqAgbE2eOT7~&f1W3VsdD44Ffe;3mJp-V@8UC z)|qnPc12o~$X-+U@L_lWqv-RtvB~%hLF($%Ew5w>^NR82qC_0FB z)=hP1-OEx?lLi#jnLzH}a;Nvr@JDO-zQWd}#k^an$Kwml;MrD&)sC5b`s0ZkVyPkb zt}-jOq^%_9>YZe7Y}PhW{a)c39G`kg(P4@kxjcYfgB4XOOcmezdUI7j-!gs7oAo2o zx(Ph{G+YZ`a%~kzK!HTAA5NXE-7vOFRr5oqY$rH>WI6SFvWmahFav!CfRMM3%8J&c z*p+%|-fNS_@QrFr(at!JY9jCg9F-%5{nb5Bo~z@Y9m&SHYV`49GAJjA5h~h4(G!Se zZmK{Bo7ivCfvl}@A-ptkFGcWXAzj3xfl{evi-OG(TaCn1FAHxRc{}B|x+Ua1D=I6M z!C^ZIvK6aS_c&(=OQDZfm>O`Nxsw{ta&yiYPA~@e#c%N>>#rq)k6Aru-qD4(D^v)y z*>Rs;YUbD1S8^D(ps6Jbj0K3wJw>L4m)0e(6Pee3Y?gy9i0^bZO?$*sv+xKV?WBlh zAp*;v6w!a8;A7sLB*g-^<$Z4L7|5jXxxP1}hQZ<55f9<^KJ>^mKlWSGaLcO0=$jem zWyZkRwe~u{{tU63DlCaS9$Y4CP4f?+wwa(&1ou)b>72ydrFvm`Rj-0`kBJgK@nd(*Eh!(NC{F-@=FnF&Y!q`7){YsLLHf0_B6aHc# z>WIuHTyJwIH{BJ4)2RtEauC7Yq7Cytc|S)4^*t8Va3HR zg=~sN^tp9re@w=GTx$;zOWMjcg-7X3Wk^N$n;&Kf1RgVG2}2L-(0o)54C509C&77i zrjSi{X*WV=%C17((N^6R4Ya*4#6s_L99RtQ>m(%#nQ#wrRC8Y%yxkH;d!MdY+Tw@r zjpSnK`;C-U{ATcgaxoEpP0Gf+tx);buOMlK=01D|J+ROu37qc*rD(w`#O=3*O*w9?biwNoq3WN1`&Wp8TvKj3C z3HR9ssH7a&Vr<6waJrU zdLg!ieYz%U^bmpn%;(V%%ugMk92&?_XX1K@mwnVSE6!&%P%Wdi7_h`CpScvspMx?N zQUR>oadnG17#hNc$pkTp+9lW+MBKHRZ~74XWUryd)4yd zj98$%XmIL4(9OnoeO5Fnyn&fpQ9b0h4e6EHHw*l68j;>(ya`g^S&y2{O8U>1*>4zR zq*WSI_2o$CHQ?x0!wl9bpx|Cm2+kFMR)oMud1%n2=qn5nE&t@Fgr#=Zv2?}wtEz^T z9rrj=?IH*qI5{G@Rn&}^Z{+TW}mQeb9=8b<_a`&Cm#n%n~ zU47MvCBsdXFB1+adOO)03+nczfWa#vwk#r{o{dF)QWya9v2nv43Zp3%Ps}($lA02*_g25t;|T{A5snSY?3A zrRQ~(Ygh_ebltHo1VCbJb*eOAr;4cnlXLvI>*$-#AVsGg6B1r7@;g^L zFlJ_th0vxO7;-opU@WAFe;<}?!2q?RBrFK5U{*ai@NLKZ^};Ul}beukveh?TQn;$%9=R+DX07m82gP$=}Uo_%&ngV`}Hyv8g{u z3SWzTGV|cwQuFIs7ZDOqO_fGf8Q`8MwL}eUp>q?4eqCmOTcwQuXtQckPy|4F1on8l zP*h>d+cH#XQf|+6c|S{7SF(Lg>bR~l(0uY?O{OEVlaxa5@e%T&xju=o1`=OD#qc16 zSvyH*my(dcp6~VqR;o(#@m44Lug@~_qw+HA=mS#Z^4reBy8iV?H~I;{LQWk3aKK8$bLRyt$g?- + + + + + diff --git a/web/src/components/basics/index.js b/web/src/components/basics/index.js new file mode 100644 index 0000000..483ea06 --- /dev/null +++ b/web/src/components/basics/index.js @@ -0,0 +1,12 @@ +import Vue from 'vue' +import TopNav from './topNav.vue' +import LbButton from './lbButton.vue' +import LbSwitch from './lbSwitch.vue' +import LbTips from './lbTips.vue' +import LbPage from './lbPage.vue' + +Vue.component('top-nav', TopNav) +Vue.component('lb-button', LbButton) +Vue.component('lb-switch', LbSwitch) +Vue.component('lb-tips', LbTips) +Vue.component('lb-page', LbPage) diff --git a/web/src/components/basics/lbButton.vue b/web/src/components/basics/lbButton.vue new file mode 100644 index 0000000..e422fb2 --- /dev/null +++ b/web/src/components/basics/lbButton.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/web/src/components/basics/lbPage.vue b/web/src/components/basics/lbPage.vue new file mode 100644 index 0000000..5f72325 --- /dev/null +++ b/web/src/components/basics/lbPage.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/web/src/components/basics/lbSwitch.vue b/web/src/components/basics/lbSwitch.vue new file mode 100644 index 0000000..60a4eca --- /dev/null +++ b/web/src/components/basics/lbSwitch.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/web/src/components/basics/lbTips.vue b/web/src/components/basics/lbTips.vue new file mode 100644 index 0000000..a105ce7 --- /dev/null +++ b/web/src/components/basics/lbTips.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/web/src/components/basics/topNav.vue b/web/src/components/basics/topNav.vue new file mode 100644 index 0000000..f2b5adf --- /dev/null +++ b/web/src/components/basics/topNav.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/web/src/components/container.vue b/web/src/components/container.vue new file mode 100644 index 0000000..0dd7041 --- /dev/null +++ b/web/src/components/container.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/web/src/components/footer.vue b/web/src/components/footer.vue new file mode 100644 index 0000000..bb55fd8 --- /dev/null +++ b/web/src/components/footer.vue @@ -0,0 +1,29 @@ + + + + + + diff --git a/web/src/components/header.vue b/web/src/components/header.vue new file mode 100644 index 0000000..2404e98 --- /dev/null +++ b/web/src/components/header.vue @@ -0,0 +1,127 @@ + + + + + + diff --git a/web/src/components/layout.vue b/web/src/components/layout.vue new file mode 100644 index 0000000..7e1633e --- /dev/null +++ b/web/src/components/layout.vue @@ -0,0 +1,67 @@ + + + + + + diff --git a/web/src/components/sidebar.vue b/web/src/components/sidebar.vue new file mode 100644 index 0000000..4a31e3e --- /dev/null +++ b/web/src/components/sidebar.vue @@ -0,0 +1,181 @@ + + + + + + diff --git a/web/src/i18n/index.js b/web/src/i18n/index.js new file mode 100644 index 0000000..9951fe0 --- /dev/null +++ b/web/src/i18n/index.js @@ -0,0 +1,15 @@ +import Vue from 'vue' +import VueI18n from 'vue-i18n' + +Vue.use(VueI18n) + +// 注册i18n实例并引入语言文件,文件格式等下解析 +const i18n = new VueI18n({ + locale: 'zh', + messages: { + 'zh': require('./langs/zh.json'), + 'en': require('./langs/en.json') + } +}) + +export default i18n diff --git a/web/src/i18n/langs/en.json b/web/src/i18n/langs/en.json new file mode 100644 index 0000000..887b1f5 --- /dev/null +++ b/web/src/i18n/langs/en.json @@ -0,0 +1,26 @@ +{ + "common":{ + "login":"Login", + "logout":"Logout", + "exit":"Exit" + }, + "action":{ + "operation": "Operation", + "view":"View", + "add": "Add", + "edit": "Edit", + "delete": "Delete", + "batchDelete": "Batch Delete", + "search": "Search", + "loading": "loading", + "submit": "Submit", + "comfirm": "Comfirm", + "cancel": "Cancel", + "reset": "Reset", + "save": "Save" + }, + "menu":{ + "set":"Business Card Set", + "businessCard": "Business Card" + } +} \ No newline at end of file diff --git a/web/src/i18n/langs/zh.json b/web/src/i18n/langs/zh.json new file mode 100644 index 0000000..30e2617 --- /dev/null +++ b/web/src/i18n/langs/zh.json @@ -0,0 +1,63 @@ +{ + "common":{ + "login":"登录", + "logout":"退出登录", + "exit":"退出" + }, + "action":{ + "operation":"操作", + "view":"查看", + "add":"添加", + "edit":"编辑", + "delete":"删除", + "batchDelete":"批量删除", + "search":"搜索", + "loading":"加载中...", + "submit":"提交", + "comfirm":"确定", + "cancel":"取消", + "reset":"重置", + "save": "保存" + }, + "menu":{ + "Survey":"概况", + "Cardset":"名片设置", + "BusinessCard":"名片", + "CardManage":"名片管理", + "ImpressionLabel":"印象标签", + "MobileSet":"手机端创建设置", + "ClientSet":"获客功能设置", + "DefaultContent":"默认内容", + "Authorisation":"强制授权", + "Malls":"商城", + "MallsManage":"商城管理", + "OrderManage":"订单管理", + "MallsSet":"商城设置", + "GoodsList":"商品列表", + "GoodsClassify":"商品分类", + "RefundManage":"退款管理", + "DealSet":"交易设置", + "OrderOvertime":"订单超时", + "VirtualPaymentSet":"虚拟支付设置", + "StaffChoiceGoods":"员工选择商品", + "PickUpSet":"自提设置", + "Dynamic":"动态", + "DynamicSet":"动态设置", + "DynamicManage":"动态管理", + "CommentManage":"评论管理", + "Website":"官网", + "WebsiteManage":"官网管理", + "ProductManage":"产品管理", + "NewsList":"新闻列表", + "CaseManage":"案例管理", + "OtherFunctions":"其他功能", + "ProClassfiyManage":"分类管理", + "NewsClassfiyManage":"分类管理", + "CaseClassfiyManage":"分类管理", + "ProductList":"产品列表", + "About":"关于我们", + "DevHistory":"发展历程", + "BusinessScope":"业务范围", + "Recruit":"人才招聘" + } +} \ No newline at end of file diff --git a/web/src/main.js b/web/src/main.js new file mode 100644 index 0000000..26be59f --- /dev/null +++ b/web/src/main.js @@ -0,0 +1,35 @@ +// The Vue build version to load with the `import` command +// (runtime-only or standalone) has been set in webpack.base.conf with an alias. +import Vue from 'vue' +import App from './App' +import ElementUI from 'element-ui' +import router from './router' +import 'element-ui/lib/theme-chalk/index.css' +import store from './store' +import {api} from './api' // 接口 +import i18n from './i18n' // 语言包 +import routes from './permission' +import './components/basics' +import reg from './utils/reg' + +Vue.config.productionTip = false +Vue.use(ElementUI, { size: 'small', zIndex: 3000 }) +Vue.prototype.$api = api +Vue.prototype.$reg = reg +router.beforeEach((to, from, next) => { + if (!store.getters.isAuth) { + store.dispatch('getUserPromission', {to, next, routes}) + } else { + next() + } +}) + +/* eslint-disable no-new */ +new Vue({ + el: '#app', + router, + i18n, + store, + components: { App }, + template: '' +}) diff --git a/web/src/permission.js b/web/src/permission.js new file mode 100644 index 0000000..43542ec --- /dev/null +++ b/web/src/permission.js @@ -0,0 +1,645 @@ +export default [ + { + path: '/', + redirect: '/survey', + hidden: true // 是否展示在侧边栏的菜单里 + }, + // 概览 + { + path: '/survey', + component: 'Layout', + redirect: '/survey/index', + meta: { + menuName: 'Survey', // 一级菜单标题 + icon: 'icon-gaikuang' // 一级菜单的图标 + }, + children: [ + { + path: 'index', + name: 'Survey', + component: '/survey/index', + meta: { + title: '', // 页面头部的标题 + isOnly: true, // 单独页面 + auth: ['view', 'add', 'edit', 'del', 'outport'], // 单独页面按钮操作权限 + pagePermission: [] + } + } + ] + }, + // 名片 + { + path: '/businessCard', + component: 'Layout', + redirect: '/businessCard/manage', + meta: { // 所有要展示在菜单里的路由都添加在这边 + menuName: 'BusinessCard', // 一级菜单标题 + icon: 'icon-mingpian', // 一级菜单的图标 + subNavName: [ + { + name: 'CardManage', // 二级菜单的下拉的标题 + url: [ + { + name: 'CardManage', // 三级菜单的标题 + url: '/businessCard/manage' // 三级菜单的路由 + }, + { + name: 'ImpressionLabel', // 三级菜单的标题 + url: '/businessCard/tag' // 三级菜单的路由 + } + ] + }, + { + name: 'Cardset', + url: [ + { + name: 'MobileSet', + url: '/businessCard/mobileSet' + }, + { + name: 'ClientSet', + url: '/businessCard/clientSet' + } + ] + } + ] + }, + children: [ // 子菜单路由表 + // 名片管理 + { + path: 'manage', + name: 'CardManage', + component: '/businessCard/manage/index', + meta: { + title: 'Cardset', // 页面头部的标题 + isOnly: false, // 多页面 + auth: [], + pagePermission: [ // 页面权限 + { + title: 'Cardset', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + }, + { + title: 'DefaultContent', + index: 1, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + }, + // 印象标签 + { + path: 'tag', + name: 'ImpressionLabel', + component: '/businessCard/manage/tag', + meta: { + title: 'Cardset', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'ImpressionLabel', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + }, + // 手机端创建设置 + { + path: 'mobileSet', + name: 'MobileSet', + component: '/businessCard/set/mobileSet', + meta: { + title: 'Cardset', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'MobileSet', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + }, + // 获客功能设置 + { + path: 'clientSet', + name: 'ClientSet', + component: '/businessCard/set/clientSet', + meta: { + title: 'Cardset', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'Authorisation', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + } + ] + }, + // 商城 + { + path: '/malls', + component: 'Layout', + redirect: '/malls/list', + meta: { // 所有要展示在菜单里的路由都添加在这边 + menuName: 'Malls', // 一级菜单标题 + icon: 'icon-gouwudai', // 一级菜单的图标 + subNavName: [ + { + name: 'MallsManage', // 二级菜单的下拉的标题 + url: [ + { + name: 'GoodsList', // 三级菜单的标题 + url: '/malls/list' // 三级菜单的路由 + }, + { + name: 'GoodsClassify', // 三级菜单的标题 + url: '/malls/classify' // 三级菜单的路由 + } + ] + }, + { + name: 'OrderManage', + url: [ + { + name: 'OrderManage', + url: '/malls/orderManage' + }, + { + name: 'RefundManage', + url: '/malls/refundManage' + } + ] + }, + { + name: 'MallsSet', + url: [ + { + name: 'DealSet', + url: '/malls/dealSet' + }, + { + name: 'VirtualPaymentSet', + url: '/malls/virtualPayment' + }, + { + name: 'StaffChoiceGoods', + url: '/malls/staffGoods' + } + ] + } + ] + }, + children: [ // 子菜单路由表 + // 商品列表 + { + path: 'list', + name: 'GoodsList', + component: '/malls/goods/list', + meta: { + title: 'MallsSet', // 页面头部的标题 + isOnly: false, // 多页面 + auth: [], + pagePermission: [ // 页面权限 + { + title: 'GoodsList', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + }, + // 商品分类 + { + path: 'classify', + name: 'GoodsClassify', + component: '/malls/goods/classify', + meta: { + title: 'MallsSet', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'GoodsClassify', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + }, + // 订单管理 + { + path: 'orderManage', + name: 'OrderManage', + component: '/malls/order/manage', + meta: { + title: 'MallsSet', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'OrderManage', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + }, + // 退款管理 + { + path: 'refundManage', + name: 'RefundManage', + component: '/malls/order/refund', + meta: { + title: 'MallsSet', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'RefundManage', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + }, + // 交易设置 + { + path: 'dealSet', + name: 'DealSet', + component: '/malls/set/deal', + meta: { + title: 'MallsSet', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'OrderOvertime', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + }, + { + title: 'PickUpSet', + index: 1, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + }, + // 虚拟支付设置 + { + path: 'virtualPayment', + name: 'VirtualPaymentSet', + component: '/malls/set/payment', + meta: { + title: 'MallsSet', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'VirtualPaymentSet', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + }, + // 员工选择商品 + { + path: 'staffGoods', + name: 'StaffChoiceGoods', + component: '/malls/set/staffGoods', + meta: { + title: 'MallsSet', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'StaffChoiceGoods', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + } + ] + }, + // 动态 + { + path: '/dynamic', + component: 'Layout', + redirect: '/dynamic/manage', + meta: { + menuName: 'Dynamic', + icon: 'icon-dongtai', + subNavName: [ + { + name: 'DynamicManage', // 二级菜单的下拉的标题 + url: [ + { + name: 'DynamicManage', // 三级菜单的标题 + url: '/dynamic/manage' // 三级菜单的路由 + }, + { + name: 'CommentManage', // 三级菜单的标题 + url: '/dynamic/comment' // 三级菜单的路由 + } + ] + } + ] + }, + children: [ + { + path: 'manage', + name: 'DynamicManage', + component: '/dynamic/manage', + meta: { + title: 'DynamicSet', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'DynamicManage', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + }, + { + path: 'comment', + name: 'CommentManage', + component: '/dynamic/comment', + meta: { + title: 'DynamicSet', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'CommentManage', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] // 按钮操作权限 + } + ] + } + } + ] + }, + // 官网 + { + path: '/website', + component: 'Layout', + redirect: '/website/proManage', + meta: { + menuName: 'Website', + icon: 'icon-guanwang', + subNavName: [ + { + name: 'ProductManage', // 产品管理 + url: [ + { + name: 'ProClassfiyManage', + url: '/website/proManage' + }, + { + name: 'ProductList', + url: '/website/proList' + } + ] + }, + { + name: 'NewsList', // 新闻列表 + url: [ + { + name: 'NewsClassfiyManage', + url: '/website/newsManage' + }, + { + name: 'NewsList', + url: '/website/newsList' + } + ] + }, + { + name: 'CaseManage', // 案列管理 + url: [ + { + name: 'CaseClassfiyManage', + url: '/website/caseManage' + }, + { + name: 'CaseManage', + url: '/website/case' + } + ] + }, + { + name: 'OtherFunctions', // 其他功能 + url: [ + { + name: 'About', + url: '/website/about' + }, + { + name: 'DevHistory', + url: '/website/history' + }, + { + name: 'BusinessScope', + url: '/website/business' + }, + { + name: 'Recruit', + url: '/website/recruit' + } + ] + } + ] + }, + children: [ + { + path: 'proManage', + name: 'ProClassfiyManage', // 产品分类管理 + component: '/website/product/manage', + meta: { + title: 'WebsiteManage', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'ProClassfiyManage', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] + } + ] + } + }, + { + path: 'proList', + name: 'ProductList', // 产品列表 + component: '/website/product/list', + meta: { + title: 'WebsiteManage', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'ProductList', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] + } + ] + } + }, + { + path: 'newsManage', + name: 'NewsClassfiyManage', // 新闻分类管理 + component: '/website/news/manage', + meta: { + title: 'WebsiteManage', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'NewsClassfiyManage', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] + } + ] + } + }, + { + path: 'newsList', + name: 'NewsList', // 新闻列表 + component: '/website/news/list', + meta: { + title: 'WebsiteManage', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'NewsList', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] + } + ] + } + }, + { + path: 'caseManage', + name: 'CaseClassfiyManage', // 案列分类管理 + component: '/website/case/manage', + meta: { + title: 'WebsiteManage', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'CaseClassfiyManage', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] + } + ] + } + }, + { + path: 'case', + name: 'CaseManage', // 案列管理 + component: '/website/case/case', + meta: { + title: 'WebsiteManage', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'CaseManage', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] + } + ] + } + }, + { + path: 'about', + name: 'About', // 其他关于我们 + component: '/website/other/about', + meta: { + title: 'WebsiteManage', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'About', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] + } + ] + } + }, + { + path: 'history', + name: 'DevHistory', // 其他发展历程 + component: '/website/other/history', + meta: { + title: 'WebsiteManage', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'DevHistory', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] + } + ] + } + }, + { + path: 'business', + name: 'BusinessScope', // 其他业务范围 + component: '/website/other/business', + meta: { + title: 'WebsiteManage', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'BusinessScope', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] + } + ] + } + }, + { + path: 'recruit', + name: 'Recruit', // 其他人才招聘 + component: '/website/other/recruit', + meta: { + title: 'WebsiteManage', + isOnly: false, + auth: [], + pagePermission: [ + { + title: 'Recruit', + index: 0, + auth: ['view', 'add', 'edit', 'del', 'outport'] + } + ] + } + } + ] + }, + { + path: '*', + redirect: '/404', + hidden: true + } +] diff --git a/web/src/router/_import_development.js b/web/src/router/_import_development.js new file mode 100644 index 0000000..684d5dc --- /dev/null +++ b/web/src/router/_import_development.js @@ -0,0 +1 @@ +module.exports = file => require('@/view' + file + '.vue').default diff --git a/web/src/router/_import_production.js b/web/src/router/_import_production.js new file mode 100644 index 0000000..331acba --- /dev/null +++ b/web/src/router/_import_production.js @@ -0,0 +1 @@ +module.exports = file => () => import('@/views/' + file + '.vue') diff --git a/web/src/router/index.js b/web/src/router/index.js new file mode 100644 index 0000000..7626443 --- /dev/null +++ b/web/src/router/index.js @@ -0,0 +1,143 @@ +import Vue from 'vue' +import Router from 'vue-router' +// import Layout from '@/components/layout' + +Vue.use(Router) + +const constantRoutes = [ + // 无权限路由 + { + path: '/login', + name: 'Login', + component: () => import('@/view/login'), + hidden: true + }, + // 404 + { + path: '/404', + name: '404', + component: () => import('@/view/404'), + hidden: true + } + // 权限路由 + // { + // path: '/', + // redirect: '/survey', + // hidden: true // 是否展示在侧边栏的菜单里 + // }, + // // 概览 + // { + // path: '/survey', + // component: Layout, + // redirect: '/survey/index', + // meta: { + // menuName: '概览', // 侧边一级菜单标题 + // icon: 'icon-gaikuang' // 侧边一级菜单的图标 + // }, + // children: [ + // { + // path: 'index', + // name: 'Survey', + // component: () => import('@/view/survey'), + // meta: { + // title: '', // 页面头部的标题 + // permission: ['view', 'add', 'edit', 'del', 'outport'] + // } + // } + // ] + // }, + // // 名片 + // { + // path: '/businessCard', + // component: Layout, + // redirect: '/businessCard/manage', + // meta: { // 所有要展示在菜单里的路由都添加在这边 + // menuName: '名片', // 侧边一级菜单标题 + // icon: 'icon-mingpian', // 侧边一级菜单的图标 + // subNavName: [ + // { + // name: '名片管理', // 二级菜单的下拉的标题 + // url: [ + // { + // name: '名片管理', // 二级菜单的子菜单的标题 + // url: '/businessCard/manage' // 二级菜单的子菜单的路由 + // }, + // { + // name: '印象标签', // 二级菜单的子菜单的标题 + // url: '/businessCard/tag' // 二级菜单的子菜单的路由 + // } + // ] + // }, + // { + // name: '名片设置', + // url: [ + // { + // name: '手机端创建设置', + // url: '/businessCard/mobileSet' + // }, + // { + // name: '获客功能设置', + // url: '/businessCard/clientSet' + // } + // ] + // } + // ] + // }, + // children: [ // 子菜单路由表 + // { + // path: 'manage', + // name: 'Manage', + // component: () => import('@/view/businessCard/manage'), + // meta: { + // title: '名片设置', // 页面头部的标题 + // permission: ['view', 'add', 'edit', 'del', 'outport'] // 操作按钮的权限 + // } + // }, + // { + // path: 'tag', + // name: 'Tag', + // component: () => import('@/view/businessCard/tag'), + // meta: { + // title: '名片设置', + // permission: ['view', 'add', 'edit', 'del', 'outport'] + // } + // }, + // { + // path: 'mobileSet', + // name: 'MobileSet', + // component: () => import('@/view/businessCard/mobileSet'), + // meta: { + // title: '名片设置', + // permission: ['view', 'add', 'edit', 'del', 'outport'] + // } + // }, + // { + // path: 'clientSet', + // name: 'ClientSet', + // component: () => import('@/view/businessCard/clientSet'), + // meta: { + // title: '名片设置', + // permission: ['view', 'add', 'edit', 'del', 'outport'] + // } + // } + // ] + // }, + // { + // path: '*', + // redirect: '/404', + // hidden: true + // } +] + +// const createRouter = () => new Router({ +// linkActiveClass: 'active', +// scrollBehavior: () => ({ y: 0 }), +// routes: constantRoutes +// }) +// const router = createRouter() constantRoutes +// 路由表 +export default new Router({ + linkActiveClass: 'active', + scrollBehavior: () => ({ y: 0 }), + routes: constantRoutes +}) diff --git a/web/src/router/test.js b/web/src/router/test.js new file mode 100644 index 0000000..ac155b6 --- /dev/null +++ b/web/src/router/test.js @@ -0,0 +1,109 @@ +const arr = [ + { + path: '/', + redirect: '/survey', + hidden: true // 是否展示在侧边栏的菜单里 + }, + // 概览 + { + path: '/survey', + component: 'Layout', + redirect: '/survey/index', + meta: { + menuName: '概览', // 侧边一级菜单标题 + icon: 'icon-gaikuang' // 侧边一级菜单的图标 + }, + children: [ + { + path: 'index', + name: 'Survey', + component: '/survey/index', + meta: { + title: '', // 页面头部的标题 + permission: ['view', 'add', 'edit', 'del', 'outport'] + } + } + ] + }, + // 名片 + { + path: '/businessCard', + component: 'Layout', + redirect: '/businessCard/manage', + meta: { // 所有要展示在菜单里的路由都添加在这边 + menuName: '名片', // 侧边一级菜单标题 + icon: 'icon-mingpian', // 侧边一级菜单的图标 + subNavName: [ + { + name: '名片管理', // 二级菜单的下拉的标题 + url: [ + { + name: '名片管理', // 二级菜单的子菜单的标题 + url: '/businessCard/manage' // 二级菜单的子菜单的路由 + }, + { + name: '印象标签', // 二级菜单的子菜单的标题 + url: '/businessCard/tag' // 二级菜单的子菜单的路由 + } + ] + }, + { + name: '名片设置', + url: [ + { + name: '手机端创建设置', + url: '/businessCard/mobileSet' + }, + { + name: '获客功能设置', + url: '/businessCard/clientSet' + } + ] + } + ] + }, + children: [ // 子菜单路由表 + { + path: 'manage', + name: 'Manage', + component: '/businessCard/manage', + meta: { + title: '名片设置', // 页面头部的标题 + permission: ['view', 'add', 'edit', 'del', 'outport'] // 操作按钮的权限 + } + }, + { + path: 'tag', + name: 'Tag', + component: '/businessCard/tag', + meta: { + title: '名片设置', + permission: ['view', 'add', 'edit', 'del', 'outport'] + } + }, + { + path: 'mobileSet', + name: 'MobileSet', + component: '/businessCard/mobileSet', + meta: { + title: '名片设置', + permission: ['view', 'add', 'edit', 'del', 'outport'] + } + }, + { + path: 'clientSet', + name: 'ClientSet', + component: '/businessCard/clientSet', + meta: { + title: '名片设置', + permission: ['view', 'add', 'edit', 'del', 'outport'] + } + } + ] + }, + { + path: '*', + redirect: '/404', + hidden: true + } +] diff --git a/web/src/store/index.js b/web/src/store/index.js new file mode 100644 index 0000000..4468350 --- /dev/null +++ b/web/src/store/index.js @@ -0,0 +1,22 @@ +import Vue from 'vue' +import Vuex from 'vuex' +import routes from './modules/routes' +import operate from './modules/operate' + +Vue.use(Vuex) + +const store = new Vuex.Store({ + modules: { + routes, + operate + }, + state: { + }, + getters: { + }, + mutations: { + }, + actions: {} +}) + +export default store diff --git a/web/src/store/modules/operate.js b/web/src/store/modules/operate.js new file mode 100644 index 0000000..2a310cb --- /dev/null +++ b/web/src/store/modules/operate.js @@ -0,0 +1,18 @@ +const state = { + currentIndex: 0 +} +const getters = { + currentIndex: state => { + return state.currentIndex + } +} +const mutations = { + setCurrentIndex (state, index = 0) { + state.currentIndex = index + } +} +export default { + state, + getters, + mutations +} diff --git a/web/src/store/modules/routes.js b/web/src/store/modules/routes.js new file mode 100644 index 0000000..d7a9fcf --- /dev/null +++ b/web/src/store/modules/routes.js @@ -0,0 +1,65 @@ +// import {api} from '@/api' +import router from '@/router' +import Layout from '@/components/layout' +const _import = require('../../router/_import_' + process.env.NODE_ENV) +const state = { + isAuth: false, + routes: [] +} +const getters = { + isAuth: state => { + return state.isAuth + }, + routes: state => { + return state.routes + } +} +const mutations = { + saveRoutes (state, routes = []) { + state.routes = routes + state.isAuth = true + } +} +const actions = { + getUserPromission ({commit}, obj) { + commit('saveRoutes', obj.routes) + routerGo(obj.routes, obj) + // 正式请求接口获取路由 + /* api.getRoutes().then(res => { + commit('saveRoutes', res.data) + routerGo(res.data, obj) + }) + */ + } +} + +export default { + state, + getters, + mutations, + actions +} + +function routerGo (routes, obj) { + let getRouter = filterAsyncRouter(routes) // 过滤路由 + router.options.routes.push(...getRouter) + router.addRoutes(getRouter) // 动态添加路由 + localStorage.setItem('routes', JSON.stringify(getRouter)) + obj.next({...obj.to, replace: true}) +} +function filterAsyncRouter (asyncRouterMap) { // 遍历后台传来的路由字符串,转换为组件对象 + const accessedRouters = asyncRouterMap.filter(route => { + if (route.component) { + if (route.component === 'Layout') { // Layout组件特殊处理 + route.component = Layout + } else { + route.component = _import(route.component) + } + } + if (route.children && route.children.length) { + route.children = filterAsyncRouter(route.children) + } + return true + }) + return accessedRouters +} diff --git a/web/src/style/icon.css b/web/src/style/icon.css new file mode 100644 index 0000000..fa17616 --- /dev/null +++ b/web/src/style/icon.css @@ -0,0 +1,132 @@ +@font-face {font-family: "iconfont"; + src: url('//at.alicdn.com/t/font_1295080_om0l8ss6y3.eot?t=1563874554993'); /* IE9 */ + src: url('//at.alicdn.com/t/font_1295080_om0l8ss6y3.eot?t=1563874554993#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAABTMAAsAAAAAJeQAABR9AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCHOAq3fKtkATYCJAN4Cz4ABCAFhG0HgjMbxR4zo8LGARBCNkD2fzjgxlB4Q+1ZCht8cI0dbLBL467Mamr+utJXozsSiUFa8z3C61sg4OFUlFM75qVweM77oZQHtM37IFKwArESjBlFGmBh4XC64fCNBWivZanrZlGudFXqohLnIlJ4+Ha+L50ngZYVWBxmbb7zgQzMtk43s+N5ukBKES+THCA4nK9N+5mk32ZI5T0CTVbFkMqKeS5wHa4WaVIDX7GumCKH42/zc/T/PPCttb/ic6754iEaSYQQyIHE67MggNxMX6fKTUbNRAVaJYS+b6Fw88352mRIqvsKM8xYmFl5d8VLrnBtB2kHzed8vs/5HSCrPb83ycknbIftENT21d4csNBsJ8y8nnB+OnVryaLnCrDI4qEKso3dKrCu9JNoM68MEACgAw+CAZGnZOQDFXAwJQQ1hlVVlALVEwBcMBYBqofM6BJ3ROYACajoVPQiAMym7x95C+EIFQAFEgZmm6onuQzEP/ZqB2a0GEFxzYPxySmaodsVGW7ISb+Z2ckXW3yjcsv6e6pkC9yd5eYRR8PGZYPHXzCBSFESKOQoRqhQZ4QxppRLyrAdPxFDxYrQb/uGNS+sxN2jZ6/Wf+fRGirk1qYyA7WZYCQxUZprjO1UVhY6W0upjb0DSQmhwGFs9xQfiNhCpCXiIkMiVaQg0iQiwoLImsgWmRK5IhnRRqJGeJAkRfwhEZFgiASiQGREjKQSkChIkiUJoEoQBag5SA6oBqQY1BiEALUDqQBVgdSBWoGMALUAGQOqA5kCkS29tciSPkkkpTeIbPjfTu0p2YEoB3BcdfGOH+o/0H2C6f5thWvVFSRcyCytlJuuUEtOj3NcOEig5CoLYNlSI5FOWbDQHFpdyipdiISwpEfBwYYFW8fBTcPYNGHgoghhws5aWR421VjafxO6Yjt1nQyGYcqhwJMnvZHAEGbFQnc+bkaysGCVeg10S0mG24KwJ6dwuCQeu0EGudkjlPXQmuRo9G3hSN+xV1BMAcoBa2AcGQ9CtIHqrxn4wZ9BsPZ75n7MnkjESlbzckvv/ew/8IpA0fJVl1aBJrUPIMLoPpwAeIRYMVL5A05Q938PbCt3Bg9wg9LnVZtbOn5eJGq2VfNwRgnYdkNHx8lz+YXyTHo6VYag1KUWEp/JWAvBSa4Nk4fteyK5xyDUbVo1afdl/66Ij2yOlYwzgiDPyOWxus/ozHoBOk2PVouuzfivJ96I8+WAneUYyGANg2FkJq/XmBvAXs+2o+ov5Y/8m/w8sd2ECxrGLoSXix6f/KuOxQ10Z8VeQFXjqfYAmuZKHm6aHsj+STmozpd7ADKRln3iAtToKYHgyIc21VxEER+GwSbEmaaCHlKopHmOjt4IrW/Uk/9FZrffVmIY2iDc1dD3a8DuvZ1/ZnE6/RpKd6vM3kL+SsTYL9t1p2svPiXP8+mKq9fcOUpVJS2bMQxZ5yjSpofqC++MR1/7vaPoDq+3/QjJBaMaLC+HkOkC2N4I7AQQyj4nqTTSdfsUBsaKBlKl1J7c2XLKpnalMjG54rZDHXpnQ7rC5e85tDfZxoVBiJuKeaBXHqrbZjFblfqrJcthRytYMSo6g4qocD6sHsqdxOx1/QTNcioMlyxhZzYddrVYslih8kX5lGMW3YhJOD6OdoVoaE6lzMRTN4tSVqirgDSa0JswZ9aYw0jBfYziqKRhoUsa0jWtyYOMsm3S8wgiTJKVqCR6YMXP6VFgrgPyxBxjMX1eQrnhypX44kfyOc8suh4pyaQ4ZL4FgHLDGZg/AQLyOCa9BhrAGnMQk7LCdKqUdXRJGbHvIa8DTux7hxW2BhMJinfYq3LA2Uvk8p6Rqed8GMULP7SXR1lr5MXbTQsNvenlAyiOnebo3fuccft2qzQnGljkUCUaUZyKXLaJWTykulAg4coGkbVPbHsUX1hh5FI7olefTuLmvrxJuOJDVE/iQee8WfUSlcqAioPgRP2uZtK9ySqsNFbDL0Y9iKZ0h/2x9ehZ93k/yVnTbU6VuIYn0a+9cmMkIj5bSYaWH4/UTRqEGEkL954C1PhsBk7XoXiqa142pbpt0b4tnC40vsXgv7ujFU0fNOwfOT5//7f8R/k1oOMnv1UL8eXz90CiL8Tk+ftkuukWwdJwuOrnE7HGqHHOh71jNJU2f04+tvtuskOoBNh6lisVebL25qCeeKX5cfSNGMr9RMNgE80zKozwJSnRFk4N+evfRnyQYBijHvSNKIGbuNJoP8/DyotRafK41jnqkKOMOGBVOx6iVHzNguLTq02w0YDTiFvoxYxQqCe6bDRHW5qk7p17yJCZjTqt0vtP/MGHMRu87tH7dUYvs+IKTpzAqcIkfnw7ibHQVosRdS/s36ICY7TC6vsy7tkqK82/1Q8tWs4f0eDubT3I7xuhn043H+f1e7R7j4Fkok7oa29H2jfy7ZUneJ/1KNfa4fLEBcXyD6eKjS5KlAYNy2eg503PQZ2hVBrg6d6VKzvAGC7dyO3O8GQktWZF3BYZHeiYTWjTw87cGeMCguXnCJGnzQCtz2brSs9GNDgv+v1Vfr73cqPAVkWKDo9Qvp781WRsdjdWDFkPh2eT2lrpzV+a2fcYXB3zEfJYKxZjQNgTC/WvVH/OLGGHtT+tFJSTeZ4y7lEECo1njR83nVv0KjpR0cUH40sHPPXZNuI2A83L2Hr98488IBqY5lLSziPSDj69lSNR0fXTgfuP0KWJ4+vwzv0LDqEDiw7ubQBsHWFu9AySl71phlUq0g/DwVIppxVpFXGE0Q+4kGOVTtVQtg9fWzTHK9FPr6j6whUII4SRnJeR+IjJkpQj/DpCdzieL9HqXgXzQpkNeodeLCoqooHbS959CpboJCQIscsnxgsT+Q7CIEUTogGkrKawYsa9JmJy9pOZOs1BmYnZNYLcmGDxTS5ntQWqr4fvQsbng2VMzwGHG4zClL8FRmG7yl8n4GLGAoWsgdPOXk03kpOz0I6TXspkXMle7PBIHLYG3biDjL99/TZ9WsICW/9A+2NhQpFUieh0iPIkN6q3ltbpckkd7Ct6lrYM54qlYi4uJXHEj5XSdGQwG6B+OnKMVvvVF05Kff2MNBWQpDiikooRfJn3wmOGI+n6Vn5fa08rVQg+Upcuw5H4KnXSanujOTF4lFK2bP3qZVnas04MPooeVB9vlK2OWDBDYDvXVjBjkpd8VcXxEnfmFZdnUsFCzgPOQkGUyxUmvdMlItqq0AnAH+/uSYuSRct2/hvc+1hcFprc0D8VFGo2ms7iunCZeNXi+lIYFCtGYGVsLuz5DZpe49PH+3qN4vyQ00JtxKCJs08eknJf3BtcbOOE7HCsxEdYDvrS+78aPCu6NVen5LSNjXFIHpA85mslfhOdUOkTTBJAwIzE63oMifQGeNVcQonIlqlk2RFT/W64xio3PkTgBlkzDfDD7/H593BQu+D3nDoJXffxUyTSqXTh3AU/RS4N4lCTSUwtqJYYC6hiE5vrymWLMWS4SGFLWJNso8iiZVnJrp0sFzNoBsuD/UQKdy1kM0szUwuMXcnAsQHEdt/Bd718uwMXKNAJkmgOFzJUVUjJ7INxUttztGXTpKC5QUZ+iqQIASuCnOwMkL4NxPHwgoqDHf187LMNR9x/13aco/KVw7fvfrFgfdQFPma8S+LzSem6OyGcDsuaX2luTZpqRVoDhrE73Mr2Wc0JzoqU9g8OH9pLGENTmB9nzX3kvZphvsjaw7pops1QeOT32hna9+Q9Rvnhw7UfHIY7gBsOYfTBfgqzzxRt6mNGjTGMaIYBuN2IO48zmfh5gYJLqxPnoxlprrPv7l+EtuG9vXgbumj/3dmuty8sMZ7AO/v6RqY0OKxfZ98YDn8tQrncxiaCQj4N+SrhkEdtnxKEhugwbrPKeU3lmsDA/I6gZcXLgjryT/Q3FGX2kT+2k8ul67+Wxmce6jKNnpdJhhRpQNZbKuXBcs3+6eXuGYomkmXzMrHGZd5aInDtE/YDd7H7A0W0ok4ZmyHUpgsVcbpeUtc1GQqCtzaQmLdW4yIVtYg/2TUNONV1iMl31iTTeeXiMJBdGhli6nPoM5ExLNMzpJnM9v8ZLuaizWmLYYz/9mYTLaVwnZMHimTD8LF4/L5pBG8Su0/iFqlo5Np5w0QCiy5OkZ6WIYxV1g25UTS03H3I2nmzy8QtKgvpKT7ZnzayRYgaIIyjKFRy+0fyHnoOe7ytEWf9uRmtyBL04jZsIijXSLnAa9g4EfmQh/Wndf7oyG1rksrsRgQ0Bf4M+PWySLWKIbA1oOWHJ2bomD8WnYhum48aBAA6VlPVxsH9nDUjk6sWDWEXuOZfFde7NuxiDU7e1iTXyeDeKzmC/fuHH8WPAB/pAFiwI9jROoze9C242YxnwRZzzxZMSk8P1nrJeswnmEah3d9G2hwP+7/97pVV/yTt31BA+0l+oom1erODt+ON++udvJ2voW8ZgU5pQQmkbMq0cjRM7SWXeRWGoWUt04gNGQhBIJlIhrkybCxSTrgycU/xy15jzi6dhMycgJxe4jV2PFO2/6hXltfRfUxpTmy7xC4rODL5sGvlxgkGZKYnsWkCb+h+8OO9SHlxYMnAJSWdYPGPBsmEePLKIQ3s1EG0OU7IuptF/ew62Pr91+bcJHfFRscOeVbao71EiiNfcoi23R8eFRvzrfofxznpqNPoXQQDu8HhI6zjdXpbVRNP2Ocd520RpvUiaUmukHYVYfYykYLoxgHrIX6iUSSUyTA2RipM2F6mDTtSKnsyyNiU25RXQaUTPWCsyK3ojHsKc7imL2sAz69JD9ju7uwHNkt6epbYdP49TTcD81vCCqdsPcB2YGV+fLYNJZXGSb++Wr3rUuH/qja5m85/xbHoOVGRwlG6tIHZ1V9s5nrQ4MF+keJVhxUpLTWny952mnNpP0ff2ew5D504kqKCiXddD0uiFkizW2Nlxd6f2xa5jyMFmNYkahDl0ud45MplPvbY2rt3IQGMRlMEvYseYQI7ytlTzE4Rw59hVZinzlLsgNlyv/ZmEBM1G8FosaCz1OZPP9shljP4PsPCWhgd422/BjmlqznyZp14fHnDwWvv05LTnOvgt3v2p4OLm4J95zO9W8ckFE8RL2Ccvb5k/HXttF7j8F6Iv3au1Pm1TBh0vF0efl/jgJOjIlPcfD5FLD2kFXOXCJJxmjwh1er5fZacOjr9TfrSqwh76dG8MP3TbcisYEEX+P9f6bnydIjvJhOjkEv423eDuNSXcFveHRK/tRd7AVPd3tOuZi5ge7l6sRY4KTONPdXpmbQFbBvocUska/doyYlaNpfNW9BxBDnSU5EhTDGInnb2RIxD1KseLfkyWXt5tetjD9em+e3YQHj9kjaEau1sdramhmjNTeyx7KZho/FuDiE1kp6SykneWDL+Ci/A1lyiP6I7L+SxaOQSrVm0j3aYe5i2ivkscrMrvUEWdT8/zZVqSIgfnGBPO0wzUulzZLzqTN5XxlXrL71kW4X6i6e1aiwiKu/5jawbz6d5BMSrwgwsa2drloENM+GcmUHtbS4drm3tNF5mB6NNLeaWIx1WhdZBnAbrDq6akythqowxjK1CvJVUb+vPACLGgB8zrJQZ+MWIjghVT2IGf3GI9IcYAr/k8+eBYvdmieSiW4oSEi4uFIuMuhdQU206qzeSUjOVJ1/e+Tfm7v3wfISv756ZeW4R07OVc/iJrJ5B0s6Xf0Q2WeXcNfPHBQnzPM1bzY2sbcurBZ+EVoucEk1zMf4Xf6Vzjnp5/EgDP8VDuZ4RfupI/Xvh9mXxGxzAxHJxpnJp2MmIy0dYTTjWzfddLlzVvBnFWs/sW4jKcnYf2trZ2b0LsTSJUykFXq+nfBAa74xiS2U+pHwQ9An7BOO6v5d7pIzTm+L2eCJj9Z4pXsocodOAUUEKReRE6986rm569h1B0Zr/hIZ7pHop1nWuEpzr2EDElnDvKoRZU1XYzLuH12JmuXcYm4nvOQmDRWh1QO64cbEkccvuO+IIviZRmuMVkn8YgQc5oSqv69PJ10SI7+xeem7/G0f5AdkYV1V7/7VzVgRFhJXIz144/+4dxQv1ouxyW5Y0nTsv/0guIshZQRK7eIsflkcOGTL1AzR9H/8BS7eTWAplOtMbMAmYddH/GBnA0kVYTJhmK7DRQLKxY+gjmO1BzwBYjOl7mMZIu+EoFpL2OMA80044hknxvqiyUXVsN/qk8LYdWGgUf52QVcmyO30JyxQdug3zzmoVGi7AohG+jTGgSNBupJn+GoBpiy7oxsJZSw3oVWL7D9QUpDfS0EFWCd+pjN9FefeKv/8hquX/m6NXNgIAFVAeRibWfzw0tQm4/86Jwwzy8NFGXHiEzj5Z/ov3QG7zWguRwUOCwVpDAYBu7QLwf2epNTwuqttO2fYXzG3nCChQwE3AgArePD4wWCABAyIFMlBBJNAhCFK7Z4AN1MIAwWkAEAiTDQEBDuwUUGBDp4ABBy7x+MBHAgns4b1ABg6CCXRQh02MDPBFquiyWqK0nijjDx7Jr9IZ9OU+v56+qZlLVDQMK611O2iqmqitq8rjh4UK/aWphJ6oTaKdusaK8Pr6cn55rWE4P8W4OmLYMAO/utYwhNDVh1bW11fHCQTlrHZDdYbhQAeZWoRS9avJy/BhsJFzVtEx0Cs/cj164N3nIlRoMEypWsEax3OqEWrVqTp9PoRBKAgV+FRZr7TGoujrNKoQDvVEKcdfLF/LYDg+pBCFMMwqMuBDNbuxIQg69UKhMoJWLQ4EZEvlqnJo9u7hp4oMB99azb7MQ9BVEqlMrlAKKrVGq9MbjCazxWqzO5wut8fr80fx920Y+GEBtAoCrxgmF7IchbQ+E4+PpO0L/LJ1OD2Q/eYYOLqZDYEMj7TlpRrWUA604lfR01egyUjFo541SdfvJitKj4LDhhr4GeB1LzMR/Lfc3JCmRUWNvGBUXFJE8UK7gbko1rz+ivLCbJYg1K52WeJPCBUilmaIkJHT0t8BAAA=') format('woff2'), + url('//at.alicdn.com/t/font_1295080_om0l8ss6y3.woff?t=1563874554993') format('woff'), + url('//at.alicdn.com/t/font_1295080_om0l8ss6y3.ttf?t=1563874554993') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ + url('//at.alicdn.com/t/font_1295080_om0l8ss6y3.svg?t=1563874554993#iconfont') format('svg'); /* iOS 4.1- */ +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-yy:before { + content: "\e634"; +} + +.icon-gouwudai:before { + content: "\e73e"; +} + +.icon-dianhua:before { + content: "\e611"; +} + +.icon-more:before { + content: "\e609"; +} + +.icon-del:before { + content: "\e608"; +} + +.icon-dingwei:before { + content: "\e63c"; +} + +.icon-youxiang:before { + content: "\e667"; +} + +.icon-warn:before { + content: "\e61e"; +} + +.icon-mingpian:before { + content: "\e651"; +} + +.icon-gaikuang:before { + content: "\e603"; +} + +.icon-xitong:before { + content: "\e67c"; +} + +.icon-weixin:before { + content: "\e62c"; +} + +.icon-dongtai:before { + content: "\e613"; +} + +.icon-kehu:before { + content: "\e604"; +} + +.icon-you:before { + content: "\e62f"; +} + +.icon-dianzan:before { + content: "\e65c"; +} + +.icon-gengduo:before { + content: "\e636"; +} + +.icon-guanwang:before { + content: "\e60e"; +} + +.icon-company:before { + content: "\e628"; +} + +.icon-bianji:before { + content: "\e649"; +} + +.icon-dianpu:before { + content: "\e678"; +} + +.icon-gongsi:before { + content: "\e712"; +} + +.icon-yuangong:before { + content: "\e673"; +} + +.icon-shuju:before { + content: "\e60d"; +} + +.icon-zhiding:before { + content: "\e76f"; +} + +.icon-liaotian:before { + content: "\e686"; +} + +.icon-liuyanguanli:before { + content: "\e665"; +} + +.icon-alike:before { + content: "\e8ad"; +} + +.icon-like:before { + content: "\e8ae"; +} diff --git a/web/src/style/reset.css b/web/src/style/reset.css new file mode 100644 index 0000000..79e821a --- /dev/null +++ b/web/src/style/reset.css @@ -0,0 +1,65 @@ +html,body,div,p,h1,h2,h3,h4,h5,h6,button,img,textarea,input,ul,li{ + margin: 0; + padding: 0; + box-sizing: border-box; + list-style: none; +} +html, +body, +#app { + width: 100%; + height: 100%; + overflow: hidden; + font-size: 14px; + color: #666666; +} + +body { + font-family: 'SourceHanSansSC-regular', 'PingFang SC', "Helvetica Neue", Helvetica, "microsoft yahei", arial, STHeiTi, sans-serif; +} + +a { + text-decoration: none +} +.page-main{ + padding: 20px; + width: 100%; + min-width: 1100px; +} + +/* .content-box { + position: absolute; + left: 176px; + right: 0; + top: 70px; + bottom: 0; + padding-bottom: 30px; + -webkit-transition: left .3s ease-in-out; + transition: left .3s ease-in-out; + background: #f0f0f0; +} + +.content { + width: auto; + height: 100%; + padding: 10px; + overflow-y: scroll; + box-sizing: border-box; + position: relative; +} +.main-common{ + width: 100%; + padding: 20px; + background: #fff; + border-radius: 5px; +} +.content-collapse { + left: 65px; +} + +.container { + padding: 30px; + background: #fff; + border: 1px solid #ddd; + border-radius: 5px; +} */ diff --git a/web/src/style/theme.scss b/web/src/style/theme.scss new file mode 100644 index 0000000..393a227 --- /dev/null +++ b/web/src/style/theme.scss @@ -0,0 +1,3 @@ +$themeColor:#19C865; +$fontColor: #666666; +$bgColor: #F4F4F4; \ No newline at end of file diff --git a/web/src/utils/reg.js b/web/src/utils/reg.js new file mode 100644 index 0000000..a182798 --- /dev/null +++ b/web/src/utils/reg.js @@ -0,0 +1,7 @@ +export default { + tel: /^1[3-9]\d{9}$/, + phone: /0\d{2,3}-\d{7,8}$/, + phone400: /^400[0-9]{7}$/, + email: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/, + number: /^\+?[0-9]*$/ +} diff --git a/web/src/view/404/index.vue b/web/src/view/404/index.vue new file mode 100644 index 0000000..8836412 --- /dev/null +++ b/web/src/view/404/index.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/web/src/view/businessCard/manage/index.vue b/web/src/view/businessCard/manage/index.vue new file mode 100644 index 0000000..ea3eaf3 --- /dev/null +++ b/web/src/view/businessCard/manage/index.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/web/src/view/businessCard/manage/subPage/cardMange.vue b/web/src/view/businessCard/manage/subPage/cardMange.vue new file mode 100644 index 0000000..0f17c23 --- /dev/null +++ b/web/src/view/businessCard/manage/subPage/cardMange.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/web/src/view/businessCard/manage/subPage/defaultContent.vue b/web/src/view/businessCard/manage/subPage/defaultContent.vue new file mode 100644 index 0000000..ba1579f --- /dev/null +++ b/web/src/view/businessCard/manage/subPage/defaultContent.vue @@ -0,0 +1,374 @@ + + + + + diff --git a/web/src/view/businessCard/manage/tag.vue b/web/src/view/businessCard/manage/tag.vue new file mode 100644 index 0000000..bb5e41f --- /dev/null +++ b/web/src/view/businessCard/manage/tag.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/web/src/view/businessCard/set/clientSet.vue b/web/src/view/businessCard/set/clientSet.vue new file mode 100644 index 0000000..473b4c9 --- /dev/null +++ b/web/src/view/businessCard/set/clientSet.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/web/src/view/businessCard/set/mobileSet.vue b/web/src/view/businessCard/set/mobileSet.vue new file mode 100644 index 0000000..cc960e1 --- /dev/null +++ b/web/src/view/businessCard/set/mobileSet.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/web/src/view/dynamic/comment.vue b/web/src/view/dynamic/comment.vue new file mode 100644 index 0000000..4fd1467 --- /dev/null +++ b/web/src/view/dynamic/comment.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/web/src/view/dynamic/manage.vue b/web/src/view/dynamic/manage.vue new file mode 100644 index 0000000..203a02e --- /dev/null +++ b/web/src/view/dynamic/manage.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/web/src/view/login/index.vue b/web/src/view/login/index.vue new file mode 100644 index 0000000..828bc39 --- /dev/null +++ b/web/src/view/login/index.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/web/src/view/malls/goods/classify.vue b/web/src/view/malls/goods/classify.vue new file mode 100644 index 0000000..c06c99a --- /dev/null +++ b/web/src/view/malls/goods/classify.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/web/src/view/malls/goods/list.vue b/web/src/view/malls/goods/list.vue new file mode 100644 index 0000000..e2d83c3 --- /dev/null +++ b/web/src/view/malls/goods/list.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/web/src/view/malls/order/manage.vue b/web/src/view/malls/order/manage.vue new file mode 100644 index 0000000..b8480f7 --- /dev/null +++ b/web/src/view/malls/order/manage.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/web/src/view/malls/order/refund.vue b/web/src/view/malls/order/refund.vue new file mode 100644 index 0000000..78b521d --- /dev/null +++ b/web/src/view/malls/order/refund.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/web/src/view/malls/set/deal.vue b/web/src/view/malls/set/deal.vue new file mode 100644 index 0000000..b1dfc04 --- /dev/null +++ b/web/src/view/malls/set/deal.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/web/src/view/malls/set/payment.vue b/web/src/view/malls/set/payment.vue new file mode 100644 index 0000000..7a9154c --- /dev/null +++ b/web/src/view/malls/set/payment.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/web/src/view/malls/set/staffGoods.vue b/web/src/view/malls/set/staffGoods.vue new file mode 100644 index 0000000..73d992c --- /dev/null +++ b/web/src/view/malls/set/staffGoods.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/web/src/view/malls/set/subpage/overTime.vue b/web/src/view/malls/set/subpage/overTime.vue new file mode 100644 index 0000000..1e32c78 --- /dev/null +++ b/web/src/view/malls/set/subpage/overTime.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/web/src/view/malls/set/subpage/pickUp.vue b/web/src/view/malls/set/subpage/pickUp.vue new file mode 100644 index 0000000..af16639 --- /dev/null +++ b/web/src/view/malls/set/subpage/pickUp.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/web/src/view/survey/index.vue b/web/src/view/survey/index.vue new file mode 100644 index 0000000..90d1301 --- /dev/null +++ b/web/src/view/survey/index.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/web/src/view/website/case/case.vue b/web/src/view/website/case/case.vue new file mode 100644 index 0000000..769cab9 --- /dev/null +++ b/web/src/view/website/case/case.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/web/src/view/website/case/manage.vue b/web/src/view/website/case/manage.vue new file mode 100644 index 0000000..769cab9 --- /dev/null +++ b/web/src/view/website/case/manage.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/web/src/view/website/news/list.vue b/web/src/view/website/news/list.vue new file mode 100644 index 0000000..769cab9 --- /dev/null +++ b/web/src/view/website/news/list.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/web/src/view/website/news/manage.vue b/web/src/view/website/news/manage.vue new file mode 100644 index 0000000..769cab9 --- /dev/null +++ b/web/src/view/website/news/manage.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/web/src/view/website/other/about.vue b/web/src/view/website/other/about.vue new file mode 100644 index 0000000..769cab9 --- /dev/null +++ b/web/src/view/website/other/about.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/web/src/view/website/other/business.vue b/web/src/view/website/other/business.vue new file mode 100644 index 0000000..769cab9 --- /dev/null +++ b/web/src/view/website/other/business.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/web/src/view/website/other/history.vue b/web/src/view/website/other/history.vue new file mode 100644 index 0000000..769cab9 --- /dev/null +++ b/web/src/view/website/other/history.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/web/src/view/website/other/recruit.vue b/web/src/view/website/other/recruit.vue new file mode 100644 index 0000000..769cab9 --- /dev/null +++ b/web/src/view/website/other/recruit.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/web/src/view/website/product/list.vue b/web/src/view/website/product/list.vue new file mode 100644 index 0000000..769cab9 --- /dev/null +++ b/web/src/view/website/product/list.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/web/src/view/website/product/manage.vue b/web/src/view/website/product/manage.vue new file mode 100644 index 0000000..769cab9 --- /dev/null +++ b/web/src/view/website/product/manage.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/web/static/.gitkeep b/web/static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/weixinpay/doc/README b/weixinpay/doc/README new file mode 100644 index 0000000..58e6e53 --- /dev/null +++ b/weixinpay/doc/README @@ -0,0 +1,76 @@ +SDK +ַ +http://paysdk.weixin.qq.com/ + +ٴָ +١װnginx+phpfpm+php +ڡSDKѹվĿ¼ +ۡ޸lib/WxPay.Config.phpΪԼ̻ŵϢ˵ +ݡ֤滻certµļ +ޡ + +SDKĿ¼ṹ +|-- cert +| |-- apiclient_cert.pem +| `-- apiclient_key.pem +|-- index.php +|-- lib +| |-- WxPay.Api.php +| |-- WxPay.Config.php +| |-- WxPay.Data.php +| |-- WxPay.Exception.php +| `-- WxPay.Notify.php +|-- logs +| |-- 2015-03-06.log +| `-- 2015-03-11.log +`-- example + |-- WxPay.JsApiPay.php + |-- WxPay.MicroPay.php + |-- WxPay.NativePay.php + |-- download.php + |-- micropay.php + |-- native.php + |-- native_notify.php + |-- notify.php + |-- orderquery.php + |-- qrcode.php + |-- refund.php + |-- refundquery.php + |-- jsapi.php + |-- log.php + `-- phpqrcode + +Ŀ¼ܼ +lib +APIӿڷװ +WxPay.Api.php ΢֧APIӿڵķװ +WxPay.Config.php ̻ +WxPay.Data.php װ +WxPay.Exception.php 쳣 +WxPay.Notify.php ص֪ͨ + +cert +֤·֤Ե¼̻ƽ̨https://pay.weixin.qq.com/index.php/account/api_cert + +example +· + +example/phpqrcode +Դάphp + +logs +־ļ + +ָ +MCHID = '1225312702'; +ʼе̻ + +APPID = 'wx426b3015555a46be'; +ʼеģ˺APPIDӦAPPID + +KEY = 'e10adc3949ba59abbe56e057f20f883e' +ʹ̻ƽ̨¼˻¼http://pay.weixin.qq.com ƽ̨õġAPIԿΪ˰ȫΪ32ַ + +APPSECRET = '01c6d59a3f9024db6336662ac95c8e74' +IJJSAPI֧openƽ̨˻ܽJSAPI֧ʱҪȡûopenidʹAPPIDӦĹƽ̨¼http://mp.weixin.qq.com ĿĻȡAppSecret + diff --git a/weixinpay/doc/README.doc b/weixinpay/doc/README.doc new file mode 100644 index 0000000000000000000000000000000000000000..e298a2c9d5a35d47cde7e7a45f8721614291c934 GIT binary patch literal 29696 zcmeI53vg6bn#aHH&Z7fq2#*9rSXw4RWJu^F1VVgFfZ&7x9iU;tRv;md4r$1Xynum` zhK&jnD8i04GE?q~lvFv7by<;eP2w6|3g0nx}_$IYihK zByFU~kSvQ%Yh;(`Ydo4ZFc{X8TP1i>cbv5ue%hgKhIZ#4Y9!Z>5$F@muLG5KXU8J` zH;6|kM5SMgLcV~%1K|m<&j<4N)qZD)h!y#d!KUerorrWR<3%cw@$F$EIq+Xb{;4b= z%&7jWc7;XXu-z;-)Tr{59bT^Tr$auJ%VUB3;WpE~i1vq<6Yi&cLX9dXG^{sn)vgdv%j>Q{bctNcCLDqXm(u196}lwXBE zgm&>dypaj7NBNbUmA@xjm9OT9P`f2YWV1sg5!ZWYIP)k!<3s%_T-i#_y|ww}!t$&V z8@$H!3e1Bkfj&#XgYTHx;t)^%)}OlyvI`tiDUJBlNtR5+r%p;_tu#ovtdta4Ene}- z8c+k@DtuC;OlqYXDINHHQarjXx%_fbbXkGNA$%p*cTr{~ zr~uVLpF>KJ`f`!co)5RQdRzHH()6Yatu4*>FPc(%!PC-wGQW12U1MuLYCX8+TjZz#;%@%thiOyWh0_Am=YbDaI)qAP(7n6EhUG}*72N`BlnuFammgqU#Vlg)9GBFoa`)EGp}TA%CtIPN?m1LSm=z}nhM|Y zZX$9@8cVtfp1G#XTi58TtqI003)bMSZS+;FH6jpLRlB?)SX-JaH6z)Tp6r^Of^a>U z8C7sZ5akC5^sjZY%R7sy z_f|C3lm|;Syj^oN)%YrW-g3PxJt13+z(xlI8zR}f2FR8oW{bN<&1Gl0k8DRNm#+(xA`Lr~;_1p+LYh&7< zZ1>K8*;D7ew)B~}!Z{M_kjtmf452x##hFFx)_1MJjZ{oq5g1vVos9A8loLO&Z5`2e zdiCW3=5ho_twT6pEH1pyduCfpajZt)8P93=I>?j#Xec`CQ8Q^nm$~%%v*@*>bRzm* zhEJ_D=`#oYoJ~4s5YAp~8{>WGi9=2=T|M7uuL|Q#&VH@J+{Usv6W}r_rk-cFiwp*C zkcJh_B#;BRoE-oXKnfCPf!Dw>Z~|NfP!RQH*ibCRoM1ed08&6Em=1D4DJTbi@I1H- z@}S|=foH+<;P>EtZ~|mu)w=+!2fM*!>RY*Z9moidi#;~gY5_Qx9|Vufn5jMclKhy42-Y*?|YggK|j_eS%$<{ z-Zf=Q!0*Z%CJzy~&gR z$DNVo(7>n1hwlyHlCHa^;9gc5LhnYd zq>g};y zyH+)SSpL{;7d)NiCX`0{A1O*|>I|XQ=YZBbQ~o;GdN1m6VP#d0)loX`w@w13@Mpj- zfs3_fpmr}(W#Gw21zPCBF%zG3y+uYpFSKsRPij&tK&}SIZktlNb>Wdh%NqLBnlMzm zzTS$^DO^eZwzXzMOY=ylv93N+xaW~k&R8wW{g8W0VdeZ-?LsS4WVzGPs)E9?>(bm- z3_vAa4|+O77T~xr*k8)E4E-q&^e`QLnTj5zq37l3?PT;S9Y=oBAb~Q(X5g5KzD_}! zaVUMNmn-n3f;?!%M$5SNw`;XnXHv^$%h`K}`-=ZRx6j$!HC>F>(tO@~+dO%6dv-8|tDtx4q&YH@>pbOA9KYcS4*=L|A!~ipX`36n7yEOR0o-OA+LHI8--`%FVk=? z=Gr|0bhm!zn*9O5b^D9pBH-G6PRLsQ9<0Z?=KgBu^go3P?5i#MmP-q5+Be1>VZ*BL zhb#Uebp9WVd7v%Rk~YsOqxWt~aqc}b?T)?n=rQ0sZMz~9!JT{Y%wey`Hhgb~Ew=3! zmTydd&4%_u0%wqhPZ@8?y!GnGYcDDW##p` zjETsxcJan@caPeT`;QYgEj{$$xH1IEo?r238dCzM1WXB-5-=rTO2CwWDFIUgrUXn0 zm=Z80@GX?UL)JJMjQgIC$v{39z^;E9x=dqA;Omos8(TO&*Q~gA9~->*tWvh}VXp`t zH;OIMmYRq?f;LH%8F}$CJJ%}n@@!I^Yl$ey8!pwkF%dPn5%vbyjj-ABJh5e4uunEm zFH^tsi7Pw0a_3Gbo^6QShO0d?(Grzt8C<^0f_E66jIay^*<-EP){0_JVx83V^L!1XR9(Ckq9YauvPE1Q&wi5(+=Vpg9K}`Nr!jXg38Ut+Xbv?ye!Jz zEuX`t?6~=Z>X}`o>}5dm7lXftMco>F?{X3<@1#G8T*j zDPSg82sVPh1wR8n2fG39r5yns;B&x|qj7I|6qpR=0w34}TET8`0K5i{gEQbN7=(L; zPB0P70EMW7O_0vo|UgIB?u;63mG_zZjrt^g}U9|z*W zXux+dQ$RYH4!#cxfDfz#E#Rl%aqtXy0lW-e2k!uC8SjHr;A22d<07~OEEqD_Mk6tR z+Q$em5;y@hk%=G;WCCg>bATJn2h>nXK?SG+)KXdiHIzf(b?^?LmhyY>0r&%;rt&ek zf)l2JK{$(}!JWVlwg8U&XsC#akwKRcGBPAirHO_D_~%2lCA1~V-ILulPuDg_1Y3(s z2fOM7Z@U=UUr8h>SB5j4Ql>;fkex;_;EJ_{UdF^E3#TJuhC7TY%2%5ZZL>=(H}#%I7PG> zHPRj`*}5N_x^){H|M(Lh$bg0{!WPe!zXF;X!#CvoGI}_Wgu5eE>(< zA|oQLwg@dtI;O4B-z(9J(JO*$Azo{$#zx8papDG%7cV+GvDK5WotB_}Odlx6Dv^;E zt0g+h8fly@^+OhVN#dvF!3$p1*xXvHPh#m~_1gPKMObO{+GliA`!rl`KGejC@9wBJ z%R@G+B}#1AJa6HA0JA+w7Ytk$eOiSa^_xH*z1<%D#Vkw`h)cC^9xn1Edd(?Lq`fr# z;ST`a{q$QtZ^QK)2!}VWI7!>$IIVXLRbON!6e5fb6ZjChL++9Zn8vfP>H2%JRDL8+ z$#e2A)>p0Z&IIRe&QZ>>&dFmeF00Gtigd-e`nw#ifv!QWc-JtOMYC$rT8tK}4bVnt zw`-%dd$nwBiMCWL(mIU0Mn+X%%P~m`WgXf++&R)|clC3{x(2x7Tti(6E<4(Hw^pc~ z)K1@WtL)LcBA>;L?@j)}0XeG9aY`k(VtDrYVsiQYD)zJdjTFoG@bVQdExhT!p(pg$ zcaS6Pno2Zna9CJ>j2LdGUW?s$O_C%h{T;jrSH}Hnhh4rqZr^bi!j>Q+4mRUA1LnjR1*RbUVhP|#dJ1s#yaYHGzXABw&;ht6 z{S@#Ly$Cp!4M$=8hDn6Y^{Wf^0ND4y#%Pcn*sPPQ!y&L2!e+f3B7X&2joxrK^Q`uj zudQrKdj9gU_IHllb!~dq>#nV@E~!c>uWjs%pO0~&%1d<_Zp_@Gzop$!ThZvuuc+{q zd7TA#=NsYKK&<6k&@{uH`0K<5{0fX7J~r-Y{vr%z#CY1A&}nn3r)?Tj0;U8^378Tv zC16Uxlz=G#Qv#+0ObM70FeUKklt54VKlN+t(SJMoSjz2jKY0}T|Ab3V=O8RHEbJV@ z_5$kh)NOeei#q!y<9kZw4X&B)6zHr+notmRyLrYZr&Rxo;NF} zICsu0_iT?I#(NIa5MBk?FP2k`I}ob8P@DZ94e%(gkFUW_L=Gb_+k$6dLv_nMm=Z80 zU`oK0fGGh}0;U8^378TvC16Uxlz=ILo0Ndkb*Z=FQM9hJQ?KQ7d+OYLUQb<{x;ph? zKA-2adH&}U^>*s?e3nn$ok}YHyhhjG89y9u>b@hu?STKXGYaszI`v{FxC4v<-vP{X zC+x9c97qCqWRLgS1pgCY#H*dE)R0{u6>tkoI$-`QU~<4sKQT?u65uvE{`$ESdw%)9 z1ohY?)EAQ=krq`tuc4?I-Z?S$e!#k4_XH09eLvIx8He{;{DCm_>+S(>pRImk4c5_zwcRvZG|^u2B3b9A&!~e*3bK2>fd$>ApLr5&B}pYhRs>r=E(EV zTQdUnQ{ueW)Z6-5HvhMaCK8(>Su3|v^4!lt?G<{P7@~eb-$>itG3h4cpU8qZdB8+o z)APQyClmE^-eTWr^g?fNn85fz{TBQ(DmRkWNj|QE6*tIvZS-K=6*Ou#j@PE>KBT() z;F}7)nfg^lymrkwGE{=!F1H0|--a)E_Bwp-=680t``w&x9@@WOUlz>uaQ|CQZJ9ov c=uUyBsoGo9n35^yrarSuU1+BL7b$`N1N@u(EdT%j literal 0 HcmV?d00001 diff --git a/weixinpay/example/WxPay.JsApiPay.php b/weixinpay/example/WxPay.JsApiPay.php new file mode 100644 index 0000000..e891eab --- /dev/null +++ b/weixinpay/example/WxPay.JsApiPay.php @@ -0,0 +1 @@ +__CreateOauthUrlForCode($baseUrl); Header("Location: $url"); exit(); } else { //获取code码,以获取openid $code = $_GET['code']; $openid = $this->getOpenidFromMp($code); return $openid; } } /** * * 获取jsapi支付的参数 * @param array $UnifiedOrderResult 统一支付接口返回的数据 * @throws WxPayException * * @return json数据,可直接填入js函数作为参数 */ public function GetJsApiParameters($UnifiedOrderResult) { if(!array_key_exists("appid", $UnifiedOrderResult)) return ['return_code' => 'fail' , 'msg' => 'not appid']; if (!array_key_exists("prepay_id", $UnifiedOrderResult)) return ['return_code' => 'fail' , 'msg' => 'not prepay_id']; if ($UnifiedOrderResult['prepay_id'] == "") { return ['return_code' => 'fail' , 'msg' => 'not prepay_id']; } $jsapi = new WxPayJsApiPay(); $jsapi->SetAppid($UnifiedOrderResult["appid"]); $timeStamp = time(); $jsapi->SetTimeStamp("$timeStamp"); $jsapi->SetNonceStr(WxPayApi::getNonceStr()); $jsapi->SetPackage("prepay_id=" . $UnifiedOrderResult['prepay_id']); $jsapi->SetSignType("MD5"); $jsapi->SetPaySign($jsapi->MakeSign()); $parameters = json_encode($jsapi->GetValues()); return $parameters; } /** * * 通过code从工作平台获取openid机器access_token * @param string $code 微信跳转回来带上的code * * @return openid */ public function GetOpenidFromMp($code) { $url = $this->__CreateOauthUrlForOpenid($code); //初始化curl $ch = curl_init(); //设置超时 curl_setopt($ch, CURLOPT_TIMEOUT,60); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,FALSE); curl_setopt($ch, CURLOPT_HEADER, FALSE); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); if(WX_CURL_PROXY_HOST != "0.0.0.0" && WX_CURL_PROXY_PORT != 0){ curl_setopt($ch,CURLOPT_PROXY, WX_CURL_PROXY_HOST); curl_setopt($ch,CURLOPT_PROXYPORT, WX_CURL_PROXY_PORT); } //运行curl,结果以jason形式返回 $res = curl_exec($ch); curl_close($ch); //取出openid $data = json_decode($res,true); // var_dump($data);die; $this->data = $data; $openid = $data['openid']; return $openid; } /** * * 拼接签名字符串 * @param array $urlObj * * @return 返回已经拼接好的字符串 */ private function ToUrlParams($urlObj) { $buff = ""; foreach ($urlObj as $k => $v) { if($k != "sign"){ $buff .= $k . "=" . $v . "&"; } } $buff = trim($buff, "&"); return $buff; } /** * * 获取地址js参数 * * @return 获取共享收货地址js函数需要的参数,json格式可以直接做参数使用 */ public function GetEditAddressParameters() { $getData = $this->data; $data = array(); $data["appid"] = WX_APPID; $data["url"] = "http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; $time = time(); $data["timestamp"] = "$time"; $data["noncestr"] = "1234568"; $data["accesstoken"] = $getData["access_token"]; ksort($data); $params = $this->ToUrlParams($data); $addrSign = sha1($params); $afterData = array( "addrSign" => $addrSign, "signType" => "sha1", "scope" => "jsapi_address", "appId" => WX_APPID, "timeStamp" => $data["timestamp"], "nonceStr" => $data["noncestr"] ); $parameters = json_encode($afterData); return $parameters; } /** * * 构造获取code的url连接 * @param string $redirectUrl 微信服务器回跳的url,需要url编码 * * @return 返回构造好的url */ private function __CreateOauthUrlForCode($redirectUrl) { $urlObj["appid"] = WX_APPID; $urlObj["redirect_uri"] = "$redirectUrl"; $urlObj["response_type"] = "code"; $urlObj["scope"] = "snsapi_base"; $urlObj["state"] = "STATE"."#wechat_redirect"; $bizString = $this->ToUrlParams($urlObj); return "https://open.weixin.qq.com/connect/oauth2/authorize?".$bizString; } /** * * 构造获取open和access_toke的url地址 * @param string $code,微信跳转带回的code * * @return 请求的url */ private function __CreateOauthUrlForOpenid($code) { $urlObj["appid"] = WX_APPID; $urlObj["secret"] = WX_APPSECRET; $urlObj["code"] = $code; $urlObj["grant_type"] = "authorization_code"; $bizString = $this->ToUrlParams($urlObj); return "https://api.weixin.qq.com/sns/oauth2/access_token?".$bizString; } } \ No newline at end of file diff --git a/weixinpay/example/WxPay.MicroPay.php b/weixinpay/example/WxPay.MicroPay.php new file mode 100644 index 0000000..b579124 --- /dev/null +++ b/weixinpay/example/WxPay.MicroPay.php @@ -0,0 +1,147 @@ +GetOut_trade_no(); + + //②、接口调用成功,明确返回调用失败 + if($result["return_code"] == "SUCCESS" && + $result["result_code"] == "FAIL" && + $result["err_code"] != "USERPAYING" && + $result["err_code"] != "SYSTEMERROR") + { + return false; + } + + //③、确认支付是否成功 + $queryTimes = 10; + while($queryTimes > 0) + { + $succResult = 0; + $queryResult = $this->query($out_trade_no, $succResult); + //如果需要等待1s后继续 + if($succResult == 2){ + sleep(2); + continue; + } else if($succResult == 1){//查询成功 + return $queryResult; + } else {//订单交易失败 + return false; + } + } + + //④、次确认失败,则撤销订单 + if(!$this->cancel($out_trade_no)) + { + throw new WxpayException("撤销单失败!"); + } + + return false; + } + + /** + * + * 查询订单情况 + * @param string $out_trade_no 商户订单号 + * @param int $succCode 查询订单结果 + * @return 0 订单不成功,1表示订单成功,2表示继续等待 + */ + public function query($out_trade_no, &$succCode) + { + $queryOrderInput = new WxPayOrderQuery(); + $queryOrderInput->SetOut_trade_no($out_trade_no); + $result = WxPayApi::orderQuery($queryOrderInput); + + if($result["return_code"] == "SUCCESS" + && $result["result_code"] == "SUCCESS") + { + //支付成功 + if($result["trade_state"] == "SUCCESS"){ + $succCode = 1; + return $result; + } + //用户支付中 + else if($result["trade_state"] == "USERPAYING"){ + $succCode = 2; + return false; + } + } + + //如果返回错误码为“此交易订单号不存在”则直接认定失败 + if($result["err_code"] == "ORDERNOTEXIST") + { + $succCode = 0; + } else{ + //如果是系统错误,则后续继续 + $succCode = 2; + } + return false; + } + + /** + * + * 撤销订单,如果失败会重复调用10次 + * @param string $out_trade_no + * @param 调用深度 $depth + */ + public function cancel($out_trade_no, $depth = 0) + { + if($depth > 10){ + return false; + } + + $clostOrder = new WxPayReverse(); + $clostOrder->SetOut_trade_no($out_trade_no); + $result = WxPayApi::reverse($clostOrder); + + //接口调用失败 + if($result["return_code"] != "SUCCESS"){ + return false; + } + + //如果结果为success且不需要重新调用撤销,则表示撤销成功 + if($result["result_code"] != "SUCCESS" + && $result["recall"] == "N"){ + return true; + } else if($result["recall"] == "Y") { + return $this->cancel($out_trade_no, ++$depth); + } + return false; + } +} \ No newline at end of file diff --git a/weixinpay/example/WxPay.NativePay.php b/weixinpay/example/WxPay.NativePay.php new file mode 100644 index 0000000..92be95c --- /dev/null +++ b/weixinpay/example/WxPay.NativePay.php @@ -0,0 +1,56 @@ +SetProduct_id($productId); + $values = WxpayApi::bizpayurl($biz); + $url = "weixin://wxpay/bizpayurl?" . $this->ToUrlParams($values); + return $url; + } + + /** + * + * 参数数组转换为url参数 + * @param array $urlObj + */ + private function ToUrlParams($urlObj) + { + $buff = ""; + foreach ($urlObj as $k => $v) + { + $buff .= $k . "=" . $v . "&"; + } + + $buff = trim($buff, "&"); + return $buff; + } + + /** + * + * 生成直接支付url,支付url有效期为2小时,模式二 + * @param UnifiedOrderInput $input + */ + public function GetPayUrl($input) + { + if($input->GetTrade_type() == "NATIVE") + { + $result = WxPayApi::unifiedOrder($input); + return $result; + } + } +} \ No newline at end of file diff --git a/weixinpay/example/download.php b/weixinpay/example/download.php new file mode 100644 index 0000000..67e3559 --- /dev/null +++ b/weixinpay/example/download.php @@ -0,0 +1,40 @@ +SetBill_date($bill_date); + $input->SetBill_type($bill_type); + $file = WxPayApi::downloadBill($input); + echo $file; + //TODO 对账单文件处理 + exit(0); +} +?> + + + + + 微信支付样例-查退款单 + + +
    +
    对账日期:

    +

    +
    账单类型:

    +

    +
    + +
    +
    + + \ No newline at end of file diff --git a/weixinpay/example/jsapi.php b/weixinpay/example/jsapi.php new file mode 100644 index 0000000..0efae35 --- /dev/null +++ b/weixinpay/example/jsapi.php @@ -0,0 +1,127 @@ +$value){ + echo "$key : $value
    "; + } +} + +//①、获取用户openid +$tools = new JsApiPay(); +$openId = $tools->GetOpenid(); + +//②、统一下单 +$input = new WxPayUnifiedOrder(); +$input->SetBody("test"); +$input->SetAttach("test"); +$input->SetOut_trade_no(WX_MCHID.date("YmdHis")); +$input->SetTotal_fee("1"); +$input->SetTime_start(date("YmdHis")); +$input->SetTime_expire(date("YmdHis", time() + 600)); +$input->SetGoods_tag("test"); +$input->SetNotify_url("http://paysdk.weixin.qq.com/example/notify.php"); +$input->SetTrade_type("JSAPI"); +$input->SetOpenid($openId); +$order = WxPayApi::unifiedOrder($input); +echo '统一下单支付单信息
    '; +printf_info($order); +$jsApiParameters = $tools->GetJsApiParameters($order); + +//获取共享收货地址js函数参数 +$editAddress = $tools->GetEditAddressParameters(); + +//③、在支持成功回调通知中处理成功之后的事宜,见 notify.php +/** + * 注意: + * 1、当你的回调地址不可访问的时候,回调通知会失败,可以通过查询订单来确认支付是否成功 + * 2、jsapi支付时需要填入用户openid,WxPay.JsApiPay.php中有获取openid流程 (文档可以参考微信公众平台“网页授权接口”, + * 参考http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html) + */ +?> + + + + + + 微信支付样例-支付 + + + + +
    + 该笔订单支付金额为1分

    +
    + +
    + + \ No newline at end of file diff --git a/weixinpay/example/log.php b/weixinpay/example/log.php new file mode 100644 index 0000000..ee851b9 --- /dev/null +++ b/weixinpay/example/log.php @@ -0,0 +1,125 @@ +handle = fopen($file,'a'); + } + + public function write($msg) + { + fwrite($this->handle, $msg, 4096); + } + + public function __destruct() + { + fclose($this->handle); + } +} + +class Log +{ + private $handler = null; + private $level = 15; + + private static $instance = null; + + private function __construct(){} + + private function __clone(){} + + public static function Init($handler = null,$level = 15) + { + if(!self::$instance instanceof self) + { + self::$instance = new self(); + self::$instance->__setHandle($handler); + self::$instance->__setLevel($level); + } + return self::$instance; + } + + + private function __setHandle($handler){ + $this->handler = $handler; + } + + private function __setLevel($level) + { + $this->level = $level; + } + + public static function DEBUG($msg) + { + self::$instance->write(1, $msg); + } + + public static function WARN($msg) + { + self::$instance->write(4, $msg); + } + + public static function ERROR($msg) + { + $debugInfo = debug_backtrace(); + $stack = "["; + foreach($debugInfo as $key => $val){ + if(array_key_exists("file", $val)){ + $stack .= ",file:" . $val["file"]; + } + if(array_key_exists("line", $val)){ + $stack .= ",line:" . $val["line"]; + } + if(array_key_exists("function", $val)){ + $stack .= ",function:" . $val["function"]; + } + } + $stack .= "]"; + self::$instance->write(8, $stack . $msg); + } + + public static function INFO($msg) + { + self::$instance->write(2, $msg); + } + + private function getLevelStr($level) + { + switch ($level) + { + case 1: + return 'debug'; + break; + case 2: + return 'info'; + break; + case 4: + return 'warn'; + break; + case 8: + return 'error'; + break; + default: + + } + } + + protected function write($level,$msg) + { + if(($level & $this->level) == $level ) + { + $msg = '['.date('Y-m-d H:i:s').']['.$this->getLevelStr($level).'] '.$msg."\n"; + $this->handler->write($msg); + } + } +} diff --git a/weixinpay/example/micropay.php b/weixinpay/example/micropay.php new file mode 100644 index 0000000..d655e69 --- /dev/null +++ b/weixinpay/example/micropay.php @@ -0,0 +1,56 @@ + + + + + 微信支付样例-查退款单 + +$value){ + echo "$key : $value
    "; + } +} + +if(isset($_REQUEST["auth_code"]) && $_REQUEST["auth_code"] != ""){ + $auth_code = $_REQUEST["auth_code"]; + $input = new WxPayMicroPay(); + $input->SetAuth_code($auth_code); + $input->SetBody("刷卡测试样例-支付"); + $input->SetTotal_fee("1"); + $input->SetOut_trade_no(WX_MCHID.date("YmdHis")); + + $microPay = new MicroPay(); + printf_info($microPay->pay($input)); +} + +/** + * 注意: + * 1、提交被扫之后,返回系统繁忙、用户输入密码等错误信息时需要循环查单以确定是否支付成功 + * 2、多次(一半10次)确认都未明确成功时需要调用撤单接口撤单,防止用户重复支付 + */ + +?> + +
    +
    商品描述:

    +

    +
    支付金额:

    +

    +
    授权码:

    +

    +
    + +
    +
    + + \ No newline at end of file diff --git a/weixinpay/example/native.php b/weixinpay/example/native.php new file mode 100644 index 0000000..82e05ef --- /dev/null +++ b/weixinpay/example/native.php @@ -0,0 +1,59 @@ +GetPrePayUrl("123456789"); + +//模式二 +/** + * 流程: + * 1、调用统一下单,取得code_url,生成二维码 + * 2、用户扫描二维码,进行支付 + * 3、支付完成之后,微信服务器会通知支付成功 + * 4、在支付成功通知中需要查单确认是否真正支付成功(见:notify.php) + */ +$input = new WxPayUnifiedOrder(); +$input->SetBody("test"); +$input->SetAttach("test"); +$input->SetOut_trade_no(WX_MCHID.date("YmdHis")); +$input->SetTotal_fee("1"); +$input->SetTime_start(date("YmdHis")); +$input->SetTime_expire(date("YmdHis", time() + 600)); +$input->SetGoods_tag("test"); +$input->SetNotify_url("http://paysdk.weixin.qq.com/example/notify.php"); +$input->SetTrade_type("NATIVE"); +$input->SetProduct_id("123456789"); +$result = $notify->GetPayUrl($input); +$url2 = $result["code_url"]; +?> + + + + + + 微信支付样例-退款 + + +
    扫描支付模式一

    + 模式一扫码支付 +


    +
    扫描支付模式二

    + 模式二扫码支付 + + + \ No newline at end of file diff --git a/weixinpay/example/native_notify.php b/weixinpay/example/native_notify.php new file mode 100644 index 0000000..fd6a711 --- /dev/null +++ b/weixinpay/example/native_notify.php @@ -0,0 +1,72 @@ +SetBody("test"); + $input->SetAttach("test"); + $input->SetOut_trade_no(WX_MCHID.date("YmdHis")); + $input->SetTotal_fee("1"); + $input->SetTime_start(date("YmdHis")); + $input->SetTime_expire(date("YmdHis", time() + 600)); + $input->SetGoods_tag("test"); + $input->SetNotify_url("http://paysdk.weixin.qq.com/example/notify.php"); + $input->SetTrade_type("NATIVE"); + $input->SetOpenid($openId); + $input->SetProduct_id($product_id); + $result = WxPayApi::unifiedOrder($input); + Log::DEBUG("unifiedorder:" . json_encode($result)); + return $result; + } + + public function NotifyProcess($data, &$msg) + { + //echo "处理回调"; + Log::DEBUG("call back:" . json_encode($data)); + + if(!array_key_exists("openid", $data) || + !array_key_exists("product_id", $data)) + { + $msg = "回调数据异常"; + return false; + } + + $openid = $data["openid"]; + $product_id = $data["product_id"]; + + //统一下单 + $result = $this->unifiedorder($openid, $product_id); + if(!array_key_exists("appid", $result) || + !array_key_exists("mch_id", $result) || + !array_key_exists("prepay_id", $result)) + { + $msg = "统一下单失败"; + return false; + } + + $this->SetData("appid", $result["appid"]); + $this->SetData("mch_id", $result["mch_id"]); + $this->SetData("nonce_str", WxPayApi::getNonceStr()); + $this->SetData("prepay_id", $result["prepay_id"]); + $this->SetData("result_code", "SUCCESS"); + $this->SetData("err_code_des", "OK"); + return true; + } +} + +Log::DEBUG("begin notify!"); +$notify = new NativeNotifyCallBack(); +$notify->Handle(true); diff --git a/weixinpay/example/notify.php b/weixinpay/example/notify.php new file mode 100644 index 0000000..090b1a3 --- /dev/null +++ b/weixinpay/example/notify.php @@ -0,0 +1,53 @@ +SetTransaction_id($transaction_id); + $result = WxPayApi::orderQuery($input); + Log::DEBUG("query:" . json_encode($result)); + if(array_key_exists("return_code", $result) + && array_key_exists("result_code", $result) + && $result["return_code"] == "SUCCESS" + && $result["result_code"] == "SUCCESS") + { + return true; + } + return false; + } + + //重写回调处理函数 + public function NotifyProcess($data, &$msg) + { + Log::DEBUG("call back:" . json_encode($data)); + $notfiyOutput = array(); + + if(!array_key_exists("transaction_id", $data)){ + $msg = "输入参数不正确"; + return false; + } + //查询订单,判断订单真实性 + if(!$this->Queryorder($data["transaction_id"])){ + $msg = "订单查询失败"; + return false; + } + return true; + } +} + +Log::DEBUG("begin notify"); +$notify = new PayNotifyCallBack(); +$notify->Handle(false); diff --git a/weixinpay/example/orderquery.php b/weixinpay/example/orderquery.php new file mode 100644 index 0000000..f7e0c4f --- /dev/null +++ b/weixinpay/example/orderquery.php @@ -0,0 +1,53 @@ + + + + + 微信支付样例-订单查询 + +$value){ + echo "$key : $value
    "; + } +} + + +if(isset($_REQUEST["transaction_id"]) && $_REQUEST["transaction_id"] != ""){ + $transaction_id = $_REQUEST["transaction_id"]; + $input = new WxPayOrderQuery(); + $input->SetTransaction_id($transaction_id); + printf_info(WxPayApi::orderQuery($input)); + exit(); +} + +if(isset($_REQUEST["out_trade_no"]) && $_REQUEST["out_trade_no"] != ""){ + $out_trade_no = $_REQUEST["out_trade_no"]; + $input = new WxPayOrderQuery(); + $input->SetOut_trade_no($out_trade_no); + printf_info(WxPayApi::orderQuery($input)); + exit(); +} +?> + +
    +
    微信订单号和商户订单号选少填一个,微信订单号优先:

    +
    微信订单号:

    +

    +
    商户订单号:

    +

    +
    + +
    +
    + + \ No newline at end of file diff --git a/weixinpay/example/phpqrcode/phpqrcode.php b/weixinpay/example/phpqrcode/phpqrcode.php new file mode 100644 index 0000000..202b2c1 --- /dev/null +++ b/weixinpay/example/phpqrcode/phpqrcode.php @@ -0,0 +1,3312 @@ + + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + + +/* + * Version: 1.1.4 + * Build: 2010100721 + */ + + + +//---- qrconst.php ----------------------------- + + + + + +/* + * PHP QR Code encoder + * + * Common constants + * + * Based on libqrencode C library distributed under LGPL 2.1 + * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010 Dominik Dzienia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + // Encoding modes + + define('QR_MODE_NUL', -1); + define('QR_MODE_NUM', 0); + define('QR_MODE_AN', 1); + define('QR_MODE_8', 2); + define('QR_MODE_KANJI', 3); + define('QR_MODE_STRUCTURE', 4); + + // Levels of error correction. + + define('QR_ECLEVEL_L', 0); + define('QR_ECLEVEL_M', 1); + define('QR_ECLEVEL_Q', 2); + define('QR_ECLEVEL_H', 3); + + // Supported output formats + + define('QR_FORMAT_TEXT', 0); + define('QR_FORMAT_PNG', 1); + + class qrstr { + public static function set(&$srctab, $x, $y, $repl, $replLen = false) { + $srctab[$y] = substr_replace($srctab[$y], ($replLen !== false)?substr($repl,0,$replLen):$repl, $x, ($replLen !== false)?$replLen:strlen($repl)); + } + } + + + +//---- merged_config.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * Config file, tuned-up for merged verion + */ + + define('QR_CACHEABLE', false); // use cache - more disk reads but less CPU power, masks and format templates are stored there + define('QR_CACHE_DIR', false); // used when QR_CACHEABLE === true + define('QR_LOG_DIR', false); // default error logs dir + + define('QR_FIND_BEST_MASK', true); // if true, estimates best mask (spec. default, but extremally slow; set to false to significant performance boost but (propably) worst quality code + define('QR_FIND_FROM_RANDOM', 2); // if false, checks all masks available, otherwise value tells count of masks need to be checked, mask id are got randomly + define('QR_DEFAULT_MASK', 2); // when QR_FIND_BEST_MASK === false + + define('QR_PNG_MAXIMUM_SIZE', 1024); // maximum allowed png image width (in pixels), tune to make sure GD and PHP can handle such big images + + + + +//---- qrtools.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * Toolset, handy and debug utilites. + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010 Dominik Dzienia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + class QRtools { + + //---------------------------------------------------------------------- + public static function binarize($frame) + { + $len = count($frame); + foreach ($frame as &$frameLine) { + + for($i=0; $i<$len; $i++) { + $frameLine[$i] = (ord($frameLine[$i])&1)?'1':'0'; + } + } + + return $frame; + } + + //---------------------------------------------------------------------- + public static function tcpdfBarcodeArray($code, $mode = 'QR,L', $tcPdfVersion = '4.5.037') + { + $barcode_array = array(); + + if (!is_array($mode)) + $mode = explode(',', $mode); + + $eccLevel = 'L'; + + if (count($mode) > 1) { + $eccLevel = $mode[1]; + } + + $qrTab = QRcode::text($code, false, $eccLevel); + $size = count($qrTab); + + $barcode_array['num_rows'] = $size; + $barcode_array['num_cols'] = $size; + $barcode_array['bcode'] = array(); + + foreach ($qrTab as $line) { + $arrAdd = array(); + foreach(str_split($line) as $char) + $arrAdd[] = ($char=='1')?1:0; + $barcode_array['bcode'][] = $arrAdd; + } + + return $barcode_array; + } + + //---------------------------------------------------------------------- + public static function clearCache() + { + self::$frames = array(); + } + + //---------------------------------------------------------------------- + public static function buildCache() + { + QRtools::markTime('before_build_cache'); + + $mask = new QRmask(); + for ($a=1; $a <= QRSPEC_VERSION_MAX; $a++) { + $frame = QRspec::newFrame($a); + if (QR_IMAGE) { + $fileName = QR_CACHE_DIR.'frame_'.$a.'.png'; + QRimage::png(self::binarize($frame), $fileName, 1, 0); + } + + $width = count($frame); + $bitMask = array_fill(0, $width, array_fill(0, $width, 0)); + for ($maskNo=0; $maskNo<8; $maskNo++) + $mask->makeMaskNo($maskNo, $width, $frame, $bitMask, true); + } + + QRtools::markTime('after_build_cache'); + } + + //---------------------------------------------------------------------- + public static function log($outfile, $err) + { + if (QR_LOG_DIR !== false) { + if ($err != '') { + if ($outfile !== false) { + file_put_contents(QR_LOG_DIR.basename($outfile).'-errors.txt', date('Y-m-d H:i:s').': '.$err, FILE_APPEND); + } else { + file_put_contents(QR_LOG_DIR.'errors.txt', date('Y-m-d H:i:s').': '.$err, FILE_APPEND); + } + } + } + } + + //---------------------------------------------------------------------- + public static function dumpMask($frame) + { + $width = count($frame); + for($y=0;$y<$width;$y++) { + for($x=0;$x<$width;$x++) { + echo ord($frame[$y][$x]).','; + } + } + } + + //---------------------------------------------------------------------- + public static function markTime($markerId) + { + list($usec, $sec) = explode(" ", microtime()); + $time = ((float)$usec + (float)$sec); + + if (!isset($GLOBALS['qr_time_bench'])) + $GLOBALS['qr_time_bench'] = array(); + + $GLOBALS['qr_time_bench'][$markerId] = $time; + } + + //---------------------------------------------------------------------- + public static function timeBenchmark() + { + self::markTime('finish'); + + $lastTime = 0; + $startTime = 0; + $p = 0; + + echo ' + + '; + + foreach($GLOBALS['qr_time_bench'] as $markerId=>$thisTime) { + if ($p > 0) { + echo ''; + } else { + $startTime = $thisTime; + } + + $p++; + $lastTime = $thisTime; + } + + echo ' + + +
    BENCHMARK
    till '.$markerId.': '.number_format($thisTime-$lastTime, 6).'s
    TOTAL: '.number_format($lastTime-$startTime, 6).'s
    '; + } + + } + + //########################################################################## + + QRtools::markTime('start'); + + + + +//---- qrspec.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * QR Code specifications + * + * Based on libqrencode C library distributed under LGPL 2.1 + * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010 Dominik Dzienia + * + * The following data / specifications are taken from + * "Two dimensional symbol -- QR-code -- Basic Specification" (JIS X0510:2004) + * or + * "Automatic identification and data capture techniques -- + * QR Code 2005 bar code symbology specification" (ISO/IEC 18004:2006) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + define('QRSPEC_VERSION_MAX', 40); + define('QRSPEC_WIDTH_MAX', 177); + + define('QRCAP_WIDTH', 0); + define('QRCAP_WORDS', 1); + define('QRCAP_REMINDER', 2); + define('QRCAP_EC', 3); + + class QRspec { + + public static $capacity = array( + array( 0, 0, 0, array( 0, 0, 0, 0)), + array( 21, 26, 0, array( 7, 10, 13, 17)), // 1 + array( 25, 44, 7, array( 10, 16, 22, 28)), + array( 29, 70, 7, array( 15, 26, 36, 44)), + array( 33, 100, 7, array( 20, 36, 52, 64)), + array( 37, 134, 7, array( 26, 48, 72, 88)), // 5 + array( 41, 172, 7, array( 36, 64, 96, 112)), + array( 45, 196, 0, array( 40, 72, 108, 130)), + array( 49, 242, 0, array( 48, 88, 132, 156)), + array( 53, 292, 0, array( 60, 110, 160, 192)), + array( 57, 346, 0, array( 72, 130, 192, 224)), //10 + array( 61, 404, 0, array( 80, 150, 224, 264)), + array( 65, 466, 0, array( 96, 176, 260, 308)), + array( 69, 532, 0, array( 104, 198, 288, 352)), + array( 73, 581, 3, array( 120, 216, 320, 384)), + array( 77, 655, 3, array( 132, 240, 360, 432)), //15 + array( 81, 733, 3, array( 144, 280, 408, 480)), + array( 85, 815, 3, array( 168, 308, 448, 532)), + array( 89, 901, 3, array( 180, 338, 504, 588)), + array( 93, 991, 3, array( 196, 364, 546, 650)), + array( 97, 1085, 3, array( 224, 416, 600, 700)), //20 + array(101, 1156, 4, array( 224, 442, 644, 750)), + array(105, 1258, 4, array( 252, 476, 690, 816)), + array(109, 1364, 4, array( 270, 504, 750, 900)), + array(113, 1474, 4, array( 300, 560, 810, 960)), + array(117, 1588, 4, array( 312, 588, 870, 1050)), //25 + array(121, 1706, 4, array( 336, 644, 952, 1110)), + array(125, 1828, 4, array( 360, 700, 1020, 1200)), + array(129, 1921, 3, array( 390, 728, 1050, 1260)), + array(133, 2051, 3, array( 420, 784, 1140, 1350)), + array(137, 2185, 3, array( 450, 812, 1200, 1440)), //30 + array(141, 2323, 3, array( 480, 868, 1290, 1530)), + array(145, 2465, 3, array( 510, 924, 1350, 1620)), + array(149, 2611, 3, array( 540, 980, 1440, 1710)), + array(153, 2761, 3, array( 570, 1036, 1530, 1800)), + array(157, 2876, 0, array( 570, 1064, 1590, 1890)), //35 + array(161, 3034, 0, array( 600, 1120, 1680, 1980)), + array(165, 3196, 0, array( 630, 1204, 1770, 2100)), + array(169, 3362, 0, array( 660, 1260, 1860, 2220)), + array(173, 3532, 0, array( 720, 1316, 1950, 2310)), + array(177, 3706, 0, array( 750, 1372, 2040, 2430)) //40 + ); + + //---------------------------------------------------------------------- + public static function getDataLength($version, $level) + { + return self::$capacity[$version][QRCAP_WORDS] - self::$capacity[$version][QRCAP_EC][$level]; + } + + //---------------------------------------------------------------------- + public static function getECCLength($version, $level) + { + return self::$capacity[$version][QRCAP_EC][$level]; + } + + //---------------------------------------------------------------------- + public static function getWidth($version) + { + return self::$capacity[$version][QRCAP_WIDTH]; + } + + //---------------------------------------------------------------------- + public static function getRemainder($version) + { + return self::$capacity[$version][QRCAP_REMINDER]; + } + + //---------------------------------------------------------------------- + public static function getMinimumVersion($size, $level) + { + + for($i=1; $i<= QRSPEC_VERSION_MAX; $i++) { + $words = self::$capacity[$i][QRCAP_WORDS] - self::$capacity[$i][QRCAP_EC][$level]; + if($words >= $size) + return $i; + } + + return -1; + } + + //###################################################################### + + public static $lengthTableBits = array( + array(10, 12, 14), + array( 9, 11, 13), + array( 8, 16, 16), + array( 8, 10, 12) + ); + + //---------------------------------------------------------------------- + public static function lengthIndicator($mode, $version) + { + if ($mode == QR_MODE_STRUCTURE) + return 0; + + if ($version <= 9) { + $l = 0; + } else if ($version <= 26) { + $l = 1; + } else { + $l = 2; + } + + return self::$lengthTableBits[$mode][$l]; + } + + //---------------------------------------------------------------------- + public static function maximumWords($mode, $version) + { + if($mode == QR_MODE_STRUCTURE) + return 3; + + if($version <= 9) { + $l = 0; + } else if($version <= 26) { + $l = 1; + } else { + $l = 2; + } + + $bits = self::$lengthTableBits[$mode][$l]; + $words = (1 << $bits) - 1; + + if($mode == QR_MODE_KANJI) { + $words *= 2; // the number of bytes is required + } + + return $words; + } + + // Error correction code ----------------------------------------------- + // Table of the error correction code (Reed-Solomon block) + // See Table 12-16 (pp.30-36), JIS X0510:2004. + + public static $eccTable = array( + array(array( 0, 0), array( 0, 0), array( 0, 0), array( 0, 0)), + array(array( 1, 0), array( 1, 0), array( 1, 0), array( 1, 0)), // 1 + array(array( 1, 0), array( 1, 0), array( 1, 0), array( 1, 0)), + array(array( 1, 0), array( 1, 0), array( 2, 0), array( 2, 0)), + array(array( 1, 0), array( 2, 0), array( 2, 0), array( 4, 0)), + array(array( 1, 0), array( 2, 0), array( 2, 2), array( 2, 2)), // 5 + array(array( 2, 0), array( 4, 0), array( 4, 0), array( 4, 0)), + array(array( 2, 0), array( 4, 0), array( 2, 4), array( 4, 1)), + array(array( 2, 0), array( 2, 2), array( 4, 2), array( 4, 2)), + array(array( 2, 0), array( 3, 2), array( 4, 4), array( 4, 4)), + array(array( 2, 2), array( 4, 1), array( 6, 2), array( 6, 2)), //10 + array(array( 4, 0), array( 1, 4), array( 4, 4), array( 3, 8)), + array(array( 2, 2), array( 6, 2), array( 4, 6), array( 7, 4)), + array(array( 4, 0), array( 8, 1), array( 8, 4), array(12, 4)), + array(array( 3, 1), array( 4, 5), array(11, 5), array(11, 5)), + array(array( 5, 1), array( 5, 5), array( 5, 7), array(11, 7)), //15 + array(array( 5, 1), array( 7, 3), array(15, 2), array( 3, 13)), + array(array( 1, 5), array(10, 1), array( 1, 15), array( 2, 17)), + array(array( 5, 1), array( 9, 4), array(17, 1), array( 2, 19)), + array(array( 3, 4), array( 3, 11), array(17, 4), array( 9, 16)), + array(array( 3, 5), array( 3, 13), array(15, 5), array(15, 10)), //20 + array(array( 4, 4), array(17, 0), array(17, 6), array(19, 6)), + array(array( 2, 7), array(17, 0), array( 7, 16), array(34, 0)), + array(array( 4, 5), array( 4, 14), array(11, 14), array(16, 14)), + array(array( 6, 4), array( 6, 14), array(11, 16), array(30, 2)), + array(array( 8, 4), array( 8, 13), array( 7, 22), array(22, 13)), //25 + array(array(10, 2), array(19, 4), array(28, 6), array(33, 4)), + array(array( 8, 4), array(22, 3), array( 8, 26), array(12, 28)), + array(array( 3, 10), array( 3, 23), array( 4, 31), array(11, 31)), + array(array( 7, 7), array(21, 7), array( 1, 37), array(19, 26)), + array(array( 5, 10), array(19, 10), array(15, 25), array(23, 25)), //30 + array(array(13, 3), array( 2, 29), array(42, 1), array(23, 28)), + array(array(17, 0), array(10, 23), array(10, 35), array(19, 35)), + array(array(17, 1), array(14, 21), array(29, 19), array(11, 46)), + array(array(13, 6), array(14, 23), array(44, 7), array(59, 1)), + array(array(12, 7), array(12, 26), array(39, 14), array(22, 41)), //35 + array(array( 6, 14), array( 6, 34), array(46, 10), array( 2, 64)), + array(array(17, 4), array(29, 14), array(49, 10), array(24, 46)), + array(array( 4, 18), array(13, 32), array(48, 14), array(42, 32)), + array(array(20, 4), array(40, 7), array(43, 22), array(10, 67)), + array(array(19, 6), array(18, 31), array(34, 34), array(20, 61)),//40 + ); + + //---------------------------------------------------------------------- + // CACHEABLE!!! + + public static function getEccSpec($version, $level, array &$spec) + { + if (count($spec) < 5) { + $spec = array(0,0,0,0,0); + } + + $b1 = self::$eccTable[$version][$level][0]; + $b2 = self::$eccTable[$version][$level][1]; + $data = self::getDataLength($version, $level); + $ecc = self::getECCLength($version, $level); + + if($b2 == 0) { + $spec[0] = $b1; + $spec[1] = (int)($data / $b1); + $spec[2] = (int)($ecc / $b1); + $spec[3] = 0; + $spec[4] = 0; + } else { + $spec[0] = $b1; + $spec[1] = (int)($data / ($b1 + $b2)); + $spec[2] = (int)($ecc / ($b1 + $b2)); + $spec[3] = $b2; + $spec[4] = $spec[1] + 1; + } + } + + // Alignment pattern --------------------------------------------------- + + // Positions of alignment patterns. + // This array includes only the second and the third position of the + // alignment patterns. Rest of them can be calculated from the distance + // between them. + + // See Table 1 in Appendix E (pp.71) of JIS X0510:2004. + + public static $alignmentPattern = array( + array( 0, 0), + array( 0, 0), array(18, 0), array(22, 0), array(26, 0), array(30, 0), // 1- 5 + array(34, 0), array(22, 38), array(24, 42), array(26, 46), array(28, 50), // 6-10 + array(30, 54), array(32, 58), array(34, 62), array(26, 46), array(26, 48), //11-15 + array(26, 50), array(30, 54), array(30, 56), array(30, 58), array(34, 62), //16-20 + array(28, 50), array(26, 50), array(30, 54), array(28, 54), array(32, 58), //21-25 + array(30, 58), array(34, 62), array(26, 50), array(30, 54), array(26, 52), //26-30 + array(30, 56), array(34, 60), array(30, 58), array(34, 62), array(30, 54), //31-35 + array(24, 50), array(28, 54), array(32, 58), array(26, 54), array(30, 58), //35-40 + ); + + + /** -------------------------------------------------------------------- + * Put an alignment marker. + * @param frame + * @param width + * @param ox,oy center coordinate of the pattern + */ + public static function putAlignmentMarker(array &$frame, $ox, $oy) + { + $finder = array( + "\xa1\xa1\xa1\xa1\xa1", + "\xa1\xa0\xa0\xa0\xa1", + "\xa1\xa0\xa1\xa0\xa1", + "\xa1\xa0\xa0\xa0\xa1", + "\xa1\xa1\xa1\xa1\xa1" + ); + + $yStart = $oy-2; + $xStart = $ox-2; + + for($y=0; $y<5; $y++) { + QRstr::set($frame, $xStart, $yStart+$y, $finder[$y]); + } + } + + //---------------------------------------------------------------------- + public static function putAlignmentPattern($version, &$frame, $width) + { + if($version < 2) + return; + + $d = self::$alignmentPattern[$version][1] - self::$alignmentPattern[$version][0]; + if($d < 0) { + $w = 2; + } else { + $w = (int)(($width - self::$alignmentPattern[$version][0]) / $d + 2); + } + + if($w * $w - 3 == 1) { + $x = self::$alignmentPattern[$version][0]; + $y = self::$alignmentPattern[$version][0]; + self::putAlignmentMarker($frame, $x, $y); + return; + } + + $cx = self::$alignmentPattern[$version][0]; + for($x=1; $x<$w - 1; $x++) { + self::putAlignmentMarker($frame, 6, $cx); + self::putAlignmentMarker($frame, $cx, 6); + $cx += $d; + } + + $cy = self::$alignmentPattern[$version][0]; + for($y=0; $y<$w-1; $y++) { + $cx = self::$alignmentPattern[$version][0]; + for($x=0; $x<$w-1; $x++) { + self::putAlignmentMarker($frame, $cx, $cy); + $cx += $d; + } + $cy += $d; + } + } + + // Version information pattern ----------------------------------------- + + // Version information pattern (BCH coded). + // See Table 1 in Appendix D (pp.68) of JIS X0510:2004. + + // size: [QRSPEC_VERSION_MAX - 6] + + public static $versionPattern = array( + 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, + 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9, + 0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75, + 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64, + 0x27541, 0x28c69 + ); + + //---------------------------------------------------------------------- + public static function getVersionPattern($version) + { + if($version < 7 || $version > QRSPEC_VERSION_MAX) + return 0; + + return self::$versionPattern[$version -7]; + } + + // Format information -------------------------------------------------- + // See calcFormatInfo in tests/test_qrspec.c (orginal qrencode c lib) + + public static $formatInfo = array( + array(0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976), + array(0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0), + array(0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed), + array(0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b) + ); + + public static function getFormatInfo($mask, $level) + { + if($mask < 0 || $mask > 7) + return 0; + + if($level < 0 || $level > 3) + return 0; + + return self::$formatInfo[$level][$mask]; + } + + // Frame --------------------------------------------------------------- + // Cache of initial frames. + + public static $frames = array(); + + /** -------------------------------------------------------------------- + * Put a finder pattern. + * @param frame + * @param width + * @param ox,oy upper-left coordinate of the pattern + */ + public static function putFinderPattern(&$frame, $ox, $oy) + { + $finder = array( + "\xc1\xc1\xc1\xc1\xc1\xc1\xc1", + "\xc1\xc0\xc0\xc0\xc0\xc0\xc1", + "\xc1\xc0\xc1\xc1\xc1\xc0\xc1", + "\xc1\xc0\xc1\xc1\xc1\xc0\xc1", + "\xc1\xc0\xc1\xc1\xc1\xc0\xc1", + "\xc1\xc0\xc0\xc0\xc0\xc0\xc1", + "\xc1\xc1\xc1\xc1\xc1\xc1\xc1" + ); + + for($y=0; $y<7; $y++) { + QRstr::set($frame, $ox, $oy+$y, $finder[$y]); + } + } + + //---------------------------------------------------------------------- + public static function createFrame($version) + { + $width = self::$capacity[$version][QRCAP_WIDTH]; + $frameLine = str_repeat ("\0", $width); + $frame = array_fill(0, $width, $frameLine); + + // Finder pattern + self::putFinderPattern($frame, 0, 0); + self::putFinderPattern($frame, $width - 7, 0); + self::putFinderPattern($frame, 0, $width - 7); + + // Separator + $yOffset = $width - 7; + + for($y=0; $y<7; $y++) { + $frame[$y][7] = "\xc0"; + $frame[$y][$width - 8] = "\xc0"; + $frame[$yOffset][7] = "\xc0"; + $yOffset++; + } + + $setPattern = str_repeat("\xc0", 8); + + QRstr::set($frame, 0, 7, $setPattern); + QRstr::set($frame, $width-8, 7, $setPattern); + QRstr::set($frame, 0, $width - 8, $setPattern); + + // Format info + $setPattern = str_repeat("\x84", 9); + QRstr::set($frame, 0, 8, $setPattern); + QRstr::set($frame, $width - 8, 8, $setPattern, 8); + + $yOffset = $width - 8; + + for($y=0; $y<8; $y++,$yOffset++) { + $frame[$y][8] = "\x84"; + $frame[$yOffset][8] = "\x84"; + } + + // Timing pattern + + for($i=1; $i<$width-15; $i++) { + $frame[6][7+$i] = chr(0x90 | ($i & 1)); + $frame[7+$i][6] = chr(0x90 | ($i & 1)); + } + + // Alignment pattern + self::putAlignmentPattern($version, $frame, $width); + + // Version information + if($version >= 7) { + $vinf = self::getVersionPattern($version); + + $v = $vinf; + + for($x=0; $x<6; $x++) { + for($y=0; $y<3; $y++) { + $frame[($width - 11)+$y][$x] = chr(0x88 | ($v & 1)); + $v = $v >> 1; + } + } + + $v = $vinf; + for($y=0; $y<6; $y++) { + for($x=0; $x<3; $x++) { + $frame[$y][$x+($width - 11)] = chr(0x88 | ($v & 1)); + $v = $v >> 1; + } + } + } + + // and a little bit... + $frame[$width - 8][8] = "\x81"; + + return $frame; + } + + //---------------------------------------------------------------------- + public static function debug($frame, $binary_mode = false) + { + if ($binary_mode) { + + foreach ($frame as &$frameLine) { + $frameLine = join('  ', explode('0', $frameLine)); + $frameLine = join('██', explode('1', $frameLine)); + } + + ?> + +


            '; + echo join("
            ", $frame); + echo '






    '; + + } else { + + foreach ($frame as &$frameLine) { + $frameLine = join(' ', explode("\xc0", $frameLine)); + $frameLine = join('', explode("\xc1", $frameLine)); + $frameLine = join(' ', explode("\xa0", $frameLine)); + $frameLine = join('', explode("\xa1", $frameLine)); + $frameLine = join('', explode("\x84", $frameLine)); //format 0 + $frameLine = join('', explode("\x85", $frameLine)); //format 1 + $frameLine = join('', explode("\x81", $frameLine)); //special bit + $frameLine = join(' ', explode("\x90", $frameLine)); //clock 0 + $frameLine = join('', explode("\x91", $frameLine)); //clock 1 + $frameLine = join(' ', explode("\x88", $frameLine)); //version + $frameLine = join('', explode("\x89", $frameLine)); //version + $frameLine = join('♦', explode("\x01", $frameLine)); + $frameLine = join('⋅', explode("\0", $frameLine)); + } + + ?> + + "; + echo join("
    ", $frame); + echo "
    "; + + } + } + + //---------------------------------------------------------------------- + public static function serial($frame) + { + return gzcompress(join("\n", $frame), 9); + } + + //---------------------------------------------------------------------- + public static function unserial($code) + { + return explode("\n", gzuncompress($code)); + } + + //---------------------------------------------------------------------- + public static function newFrame($version) + { + if($version < 1 || $version > QRSPEC_VERSION_MAX) + return null; + + if(!isset(self::$frames[$version])) { + + $fileName = QR_CACHE_DIR.'frame_'.$version.'.dat'; + + if (QR_CACHEABLE) { + if (file_exists($fileName)) { + self::$frames[$version] = self::unserial(file_get_contents($fileName)); + } else { + self::$frames[$version] = self::createFrame($version); + file_put_contents($fileName, self::serial(self::$frames[$version])); + } + } else { + self::$frames[$version] = self::createFrame($version); + } + } + + if(is_null(self::$frames[$version])) + return null; + + return self::$frames[$version]; + } + + //---------------------------------------------------------------------- + public static function rsBlockNum($spec) { return $spec[0] + $spec[3]; } + public static function rsBlockNum1($spec) { return $spec[0]; } + public static function rsDataCodes1($spec) { return $spec[1]; } + public static function rsEccCodes1($spec) { return $spec[2]; } + public static function rsBlockNum2($spec) { return $spec[3]; } + public static function rsDataCodes2($spec) { return $spec[4]; } + public static function rsEccCodes2($spec) { return $spec[2]; } + public static function rsDataLength($spec) { return ($spec[0] * $spec[1]) + ($spec[3] * $spec[4]); } + public static function rsEccLength($spec) { return ($spec[0] + $spec[3]) * $spec[2]; } + + } + + + +//---- qrimage.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * Image output of code using GD2 + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010 Dominik Dzienia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + define('QR_IMAGE', true); + + class QRimage { + + //---------------------------------------------------------------------- + public static function png($frame, $filename = false, $pixelPerPoint = 4, $outerFrame = 4,$saveandprint=FALSE) + { + $image = self::image($frame, $pixelPerPoint, $outerFrame); + + if ($filename === false) { + Header("Content-type: image/png"); + ImagePng($image); + } else { + if($saveandprint===TRUE){ + ImagePng($image, $filename); + header("Content-type: image/png"); + ImagePng($image); + }else{ + ImagePng($image, $filename); + } + } + + ImageDestroy($image); + } + + //---------------------------------------------------------------------- + public static function jpg($frame, $filename = false, $pixelPerPoint = 8, $outerFrame = 4, $q = 85) + { + $image = self::image($frame, $pixelPerPoint, $outerFrame); + + if ($filename === false) { + Header("Content-type: image/jpeg"); + ImageJpeg($image, null, $q); + } else { + ImageJpeg($image, $filename, $q); + } + + ImageDestroy($image); + } + + //---------------------------------------------------------------------- + private static function image($frame, $pixelPerPoint = 4, $outerFrame = 4) + { + $h = count($frame); + $w = strlen($frame[0]); + + $imgW = $w + 2*$outerFrame; + $imgH = $h + 2*$outerFrame; + + $base_image =ImageCreate($imgW, $imgH); + + $col[0] = ImageColorAllocate($base_image,255,255,255); + $col[1] = ImageColorAllocate($base_image,0,0,0); + + imagefill($base_image, 0, 0, $col[0]); + + for($y=0; $y<$h; $y++) { + for($x=0; $x<$w; $x++) { + if ($frame[$y][$x] == '1') { + ImageSetPixel($base_image,$x+$outerFrame,$y+$outerFrame,$col[1]); + } + } + } + + $target_image =ImageCreate($imgW * $pixelPerPoint, $imgH * $pixelPerPoint); + ImageCopyResized($target_image, $base_image, 0, 0, 0, 0, $imgW * $pixelPerPoint, $imgH * $pixelPerPoint, $imgW, $imgH); + ImageDestroy($base_image); + + return $target_image; + } + } + + + +//---- qrinput.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * Input encoding class + * + * Based on libqrencode C library distributed under LGPL 2.1 + * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010 Dominik Dzienia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + define('STRUCTURE_HEADER_BITS', 20); + define('MAX_STRUCTURED_SYMBOLS', 16); + + class QRinputItem { + + public $mode; + public $size; + public $data; + public $bstream; + + public function __construct($mode, $size, $data, $bstream = null) + { + $setData = array_slice($data, 0, $size); + + if (count($setData) < $size) { + $setData = array_merge($setData, array_fill(0,$size-count($setData),0)); + } + + if(!QRinput::check($mode, $size, $setData)) { + throw new Exception('Error m:'.$mode.',s:'.$size.',d:'.join(',',$setData)); + return null; + } + + $this->mode = $mode; + $this->size = $size; + $this->data = $setData; + $this->bstream = $bstream; + } + + //---------------------------------------------------------------------- + public function encodeModeNum($version) + { + try { + + $words = (int)($this->size / 3); + $bs = new QRbitstream(); + + $val = 0x1; + $bs->appendNum(4, $val); + $bs->appendNum(QRspec::lengthIndicator(QR_MODE_NUM, $version), $this->size); + + for($i=0; $i<$words; $i++) { + $val = (ord($this->data[$i*3 ]) - ord('0')) * 100; + $val += (ord($this->data[$i*3+1]) - ord('0')) * 10; + $val += (ord($this->data[$i*3+2]) - ord('0')); + $bs->appendNum(10, $val); + } + + if($this->size - $words * 3 == 1) { + $val = ord($this->data[$words*3]) - ord('0'); + $bs->appendNum(4, $val); + } else if($this->size - $words * 3 == 2) { + $val = (ord($this->data[$words*3 ]) - ord('0')) * 10; + $val += (ord($this->data[$words*3+1]) - ord('0')); + $bs->appendNum(7, $val); + } + + $this->bstream = $bs; + return 0; + + } catch (Exception $e) { + return -1; + } + } + + //---------------------------------------------------------------------- + public function encodeModeAn($version) + { + try { + $words = (int)($this->size / 2); + $bs = new QRbitstream(); + + $bs->appendNum(4, 0x02); + $bs->appendNum(QRspec::lengthIndicator(QR_MODE_AN, $version), $this->size); + + for($i=0; $i<$words; $i++) { + $val = (int)QRinput::lookAnTable(ord($this->data[$i*2 ])) * 45; + $val += (int)QRinput::lookAnTable(ord($this->data[$i*2+1])); + + $bs->appendNum(11, $val); + } + + if($this->size & 1) { + $val = QRinput::lookAnTable(ord($this->data[$words * 2])); + $bs->appendNum(6, $val); + } + + $this->bstream = $bs; + return 0; + + } catch (Exception $e) { + return -1; + } + } + + //---------------------------------------------------------------------- + public function encodeMode8($version) + { + try { + $bs = new QRbitstream(); + + $bs->appendNum(4, 0x4); + $bs->appendNum(QRspec::lengthIndicator(QR_MODE_8, $version), $this->size); + + for($i=0; $i<$this->size; $i++) { + $bs->appendNum(8, ord($this->data[$i])); + } + + $this->bstream = $bs; + return 0; + + } catch (Exception $e) { + return -1; + } + } + + //---------------------------------------------------------------------- + public function encodeModeKanji($version) + { + try { + + $bs = new QRbitrtream(); + + $bs->appendNum(4, 0x8); + $bs->appendNum(QRspec::lengthIndicator(QR_MODE_KANJI, $version), (int)($this->size / 2)); + + for($i=0; $i<$this->size; $i+=2) { + $val = (ord($this->data[$i]) << 8) | ord($this->data[$i+1]); + if($val <= 0x9ffc) { + $val -= 0x8140; + } else { + $val -= 0xc140; + } + + $h = ($val >> 8) * 0xc0; + $val = ($val & 0xff) + $h; + + $bs->appendNum(13, $val); + } + + $this->bstream = $bs; + return 0; + + } catch (Exception $e) { + return -1; + } + } + + //---------------------------------------------------------------------- + public function encodeModeStructure() + { + try { + $bs = new QRbitstream(); + + $bs->appendNum(4, 0x03); + $bs->appendNum(4, ord($this->data[1]) - 1); + $bs->appendNum(4, ord($this->data[0]) - 1); + $bs->appendNum(8, ord($this->data[2])); + + $this->bstream = $bs; + return 0; + + } catch (Exception $e) { + return -1; + } + } + + //---------------------------------------------------------------------- + public function estimateBitStreamSizeOfEntry($version) + { + $bits = 0; + + if($version == 0) + $version = 1; + + switch($this->mode) { + case QR_MODE_NUM: $bits = QRinput::estimateBitsModeNum($this->size); break; + case QR_MODE_AN: $bits = QRinput::estimateBitsModeAn($this->size); break; + case QR_MODE_8: $bits = QRinput::estimateBitsMode8($this->size); break; + case QR_MODE_KANJI: $bits = QRinput::estimateBitsModeKanji($this->size);break; + case QR_MODE_STRUCTURE: return STRUCTURE_HEADER_BITS; + default: + return 0; + } + + $l = QRspec::lengthIndicator($this->mode, $version); + $m = 1 << $l; + $num = (int)(($this->size + $m - 1) / $m); + + $bits += $num * (4 + $l); + + return $bits; + } + + //---------------------------------------------------------------------- + public function encodeBitStream($version) + { + try { + + unset($this->bstream); + $words = QRspec::maximumWords($this->mode, $version); + + if($this->size > $words) { + + $st1 = new QRinputItem($this->mode, $words, $this->data); + $st2 = new QRinputItem($this->mode, $this->size - $words, array_slice($this->data, $words)); + + $st1->encodeBitStream($version); + $st2->encodeBitStream($version); + + $this->bstream = new QRbitstream(); + $this->bstream->append($st1->bstream); + $this->bstream->append($st2->bstream); + + unset($st1); + unset($st2); + + } else { + + $ret = 0; + + switch($this->mode) { + case QR_MODE_NUM: $ret = $this->encodeModeNum($version); break; + case QR_MODE_AN: $ret = $this->encodeModeAn($version); break; + case QR_MODE_8: $ret = $this->encodeMode8($version); break; + case QR_MODE_KANJI: $ret = $this->encodeModeKanji($version);break; + case QR_MODE_STRUCTURE: $ret = $this->encodeModeStructure(); break; + + default: + break; + } + + if($ret < 0) + return -1; + } + + return $this->bstream->size(); + + } catch (Exception $e) { + return -1; + } + } + }; + + //########################################################################## + + class QRinput { + + public $items; + + private $version; + private $level; + + //---------------------------------------------------------------------- + public function __construct($version = 0, $level = QR_ECLEVEL_L) + { + if ($version < 0 || $version > QRSPEC_VERSION_MAX || $level > QR_ECLEVEL_H) { + throw new Exception('Invalid version no'); + return NULL; + } + + $this->version = $version; + $this->level = $level; + } + + //---------------------------------------------------------------------- + public function getVersion() + { + return $this->version; + } + + //---------------------------------------------------------------------- + public function setVersion($version) + { + if($version < 0 || $version > QRSPEC_VERSION_MAX) { + throw new Exception('Invalid version no'); + return -1; + } + + $this->version = $version; + + return 0; + } + + //---------------------------------------------------------------------- + public function getErrorCorrectionLevel() + { + return $this->level; + } + + //---------------------------------------------------------------------- + public function setErrorCorrectionLevel($level) + { + if($level > QR_ECLEVEL_H) { + throw new Exception('Invalid ECLEVEL'); + return -1; + } + + $this->level = $level; + + return 0; + } + + //---------------------------------------------------------------------- + public function appendEntry(QRinputItem $entry) + { + $this->items[] = $entry; + } + + //---------------------------------------------------------------------- + public function append($mode, $size, $data) + { + try { + $entry = new QRinputItem($mode, $size, $data); + $this->items[] = $entry; + return 0; + } catch (Exception $e) { + return -1; + } + } + + //---------------------------------------------------------------------- + + public function insertStructuredAppendHeader($size, $index, $parity) + { + if( $size > MAX_STRUCTURED_SYMBOLS ) { + throw new Exception('insertStructuredAppendHeader wrong size'); + } + + if( $index <= 0 || $index > MAX_STRUCTURED_SYMBOLS ) { + throw new Exception('insertStructuredAppendHeader wrong index'); + } + + $buf = array($size, $index, $parity); + + try { + $entry = new QRinputItem(QR_MODE_STRUCTURE, 3, buf); + array_unshift($this->items, $entry); + return 0; + } catch (Exception $e) { + return -1; + } + } + + //---------------------------------------------------------------------- + public function calcParity() + { + $parity = 0; + + foreach($this->items as $item) { + if($item->mode != QR_MODE_STRUCTURE) { + for($i=$item->size-1; $i>=0; $i--) { + $parity ^= $item->data[$i]; + } + } + } + + return $parity; + } + + //---------------------------------------------------------------------- + public static function checkModeNum($size, $data) + { + for($i=0; $i<$size; $i++) { + if((ord($data[$i]) < ord('0')) || (ord($data[$i]) > ord('9'))){ + return false; + } + } + + return true; + } + + //---------------------------------------------------------------------- + public static function estimateBitsModeNum($size) + { + $w = (int)$size / 3; + $bits = $w * 10; + + switch($size - $w * 3) { + case 1: + $bits += 4; + break; + case 2: + $bits += 7; + break; + default: + break; + } + + return $bits; + } + + //---------------------------------------------------------------------- + public static $anTable = array( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + ); + + //---------------------------------------------------------------------- + public static function lookAnTable($c) + { + return (($c > 127)?-1:self::$anTable[$c]); + } + + //---------------------------------------------------------------------- + public static function checkModeAn($size, $data) + { + for($i=0; $i<$size; $i++) { + if (self::lookAnTable(ord($data[$i])) == -1) { + return false; + } + } + + return true; + } + + //---------------------------------------------------------------------- + public static function estimateBitsModeAn($size) + { + $w = (int)($size / 2); + $bits = $w * 11; + + if($size & 1) { + $bits += 6; + } + + return $bits; + } + + //---------------------------------------------------------------------- + public static function estimateBitsMode8($size) + { + return $size * 8; + } + + //---------------------------------------------------------------------- + public function estimateBitsModeKanji($size) + { + return (int)(($size / 2) * 13); + } + + //---------------------------------------------------------------------- + public static function checkModeKanji($size, $data) + { + if($size & 1) + return false; + + for($i=0; $i<$size; $i+=2) { + $val = (ord($data[$i]) << 8) | ord($data[$i+1]); + if( $val < 0x8140 + || ($val > 0x9ffc && $val < 0xe040) + || $val > 0xebbf) { + return false; + } + } + + return true; + } + + /*********************************************************************** + * Validation + **********************************************************************/ + + public static function check($mode, $size, $data) + { + if($size <= 0) + return false; + + switch($mode) { + case QR_MODE_NUM: return self::checkModeNum($size, $data); break; + case QR_MODE_AN: return self::checkModeAn($size, $data); break; + case QR_MODE_KANJI: return self::checkModeKanji($size, $data); break; + case QR_MODE_8: return true; break; + case QR_MODE_STRUCTURE: return true; break; + + default: + break; + } + + return false; + } + + + //---------------------------------------------------------------------- + public function estimateBitStreamSize($version) + { + $bits = 0; + + foreach($this->items as $item) { + $bits += $item->estimateBitStreamSizeOfEntry($version); + } + + return $bits; + } + + //---------------------------------------------------------------------- + public function estimateVersion() + { + $version = 0; + $prev = 0; + do { + $prev = $version; + $bits = $this->estimateBitStreamSize($prev); + $version = QRspec::getMinimumVersion((int)(($bits + 7) / 8), $this->level); + if ($version < 0) { + return -1; + } + } while ($version > $prev); + + return $version; + } + + //---------------------------------------------------------------------- + public static function lengthOfCode($mode, $version, $bits) + { + $payload = $bits - 4 - QRspec::lengthIndicator($mode, $version); + switch($mode) { + case QR_MODE_NUM: + $chunks = (int)($payload / 10); + $remain = $payload - $chunks * 10; + $size = $chunks * 3; + if($remain >= 7) { + $size += 2; + } else if($remain >= 4) { + $size += 1; + } + break; + case QR_MODE_AN: + $chunks = (int)($payload / 11); + $remain = $payload - $chunks * 11; + $size = $chunks * 2; + if($remain >= 6) + $size++; + break; + case QR_MODE_8: + $size = (int)($payload / 8); + break; + case QR_MODE_KANJI: + $size = (int)(($payload / 13) * 2); + break; + case QR_MODE_STRUCTURE: + $size = (int)($payload / 8); + break; + default: + $size = 0; + break; + } + + $maxsize = QRspec::maximumWords($mode, $version); + if($size < 0) $size = 0; + if($size > $maxsize) $size = $maxsize; + + return $size; + } + + //---------------------------------------------------------------------- + public function createBitStream() + { + $total = 0; + + foreach($this->items as $item) { + $bits = $item->encodeBitStream($this->version); + + if($bits < 0) + return -1; + + $total += $bits; + } + + return $total; + } + + //---------------------------------------------------------------------- + public function convertData() + { + $ver = $this->estimateVersion(); + if($ver > $this->getVersion()) { + $this->setVersion($ver); + } + + for(;;) { + $bits = $this->createBitStream(); + + if($bits < 0) + return -1; + + $ver = QRspec::getMinimumVersion((int)(($bits + 7) / 8), $this->level); + if($ver < 0) { + throw new Exception('WRONG VERSION'); + return -1; + } else if($ver > $this->getVersion()) { + $this->setVersion($ver); + } else { + break; + } + } + + return 0; + } + + //---------------------------------------------------------------------- + public function appendPaddingBit(&$bstream) + { + $bits = $bstream->size(); + $maxwords = QRspec::getDataLength($this->version, $this->level); + $maxbits = $maxwords * 8; + + if ($maxbits == $bits) { + return 0; + } + + if ($maxbits - $bits < 5) { + return $bstream->appendNum($maxbits - $bits, 0); + } + + $bits += 4; + $words = (int)(($bits + 7) / 8); + + $padding = new QRbitstream(); + $ret = $padding->appendNum($words * 8 - $bits + 4, 0); + + if($ret < 0) + return $ret; + + $padlen = $maxwords - $words; + + if($padlen > 0) { + + $padbuf = array(); + for($i=0; $i<$padlen; $i++) { + $padbuf[$i] = ($i&1)?0x11:0xec; + } + + $ret = $padding->appendBytes($padlen, $padbuf); + + if($ret < 0) + return $ret; + + } + + $ret = $bstream->append($padding); + + return $ret; + } + + //---------------------------------------------------------------------- + public function mergeBitStream() + { + if($this->convertData() < 0) { + return null; + } + + $bstream = new QRbitstream(); + + foreach($this->items as $item) { + $ret = $bstream->append($item->bstream); + if($ret < 0) { + return null; + } + } + + return $bstream; + } + + //---------------------------------------------------------------------- + public function getBitStream() + { + + $bstream = $this->mergeBitStream(); + + if($bstream == null) { + return null; + } + + $ret = $this->appendPaddingBit($bstream); + if($ret < 0) { + return null; + } + + return $bstream; + } + + //---------------------------------------------------------------------- + public function getByteStream() + { + $bstream = $this->getBitStream(); + if($bstream == null) { + return null; + } + + return $bstream->toByte(); + } + } + + + + + + +//---- qrbitstream.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * Bitstream class + * + * Based on libqrencode C library distributed under LGPL 2.1 + * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010 Dominik Dzienia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + class QRbitstream { + + public $data = array(); + + //---------------------------------------------------------------------- + public function size() + { + return count($this->data); + } + + //---------------------------------------------------------------------- + public function allocate($setLength) + { + $this->data = array_fill(0, $setLength, 0); + return 0; + } + + //---------------------------------------------------------------------- + public static function newFromNum($bits, $num) + { + $bstream = new QRbitstream(); + $bstream->allocate($bits); + + $mask = 1 << ($bits - 1); + for($i=0; $i<$bits; $i++) { + if($num & $mask) { + $bstream->data[$i] = 1; + } else { + $bstream->data[$i] = 0; + } + $mask = $mask >> 1; + } + + return $bstream; + } + + //---------------------------------------------------------------------- + public static function newFromBytes($size, $data) + { + $bstream = new QRbitstream(); + $bstream->allocate($size * 8); + $p=0; + + for($i=0; $i<$size; $i++) { + $mask = 0x80; + for($j=0; $j<8; $j++) { + if($data[$i] & $mask) { + $bstream->data[$p] = 1; + } else { + $bstream->data[$p] = 0; + } + $p++; + $mask = $mask >> 1; + } + } + + return $bstream; + } + + //---------------------------------------------------------------------- + public function append(QRbitstream $arg) + { + if (is_null($arg)) { + return -1; + } + + if($arg->size() == 0) { + return 0; + } + + if($this->size() == 0) { + $this->data = $arg->data; + return 0; + } + + $this->data = array_values(array_merge($this->data, $arg->data)); + + return 0; + } + + //---------------------------------------------------------------------- + public function appendNum($bits, $num) + { + if ($bits == 0) + return 0; + + $b = QRbitstream::newFromNum($bits, $num); + + if(is_null($b)) + return -1; + + $ret = $this->append($b); + unset($b); + + return $ret; + } + + //---------------------------------------------------------------------- + public function appendBytes($size, $data) + { + if ($size == 0) + return 0; + + $b = QRbitstream::newFromBytes($size, $data); + + if(is_null($b)) + return -1; + + $ret = $this->append($b); + unset($b); + + return $ret; + } + + //---------------------------------------------------------------------- + public function toByte() + { + + $size = $this->size(); + + if($size == 0) { + return array(); + } + + $data = array_fill(0, (int)(($size + 7) / 8), 0); + $bytes = (int)($size / 8); + + $p = 0; + + for($i=0; $i<$bytes; $i++) { + $v = 0; + for($j=0; $j<8; $j++) { + $v = $v << 1; + $v |= $this->data[$p]; + $p++; + } + $data[$i] = $v; + } + + if($size & 7) { + $v = 0; + for($j=0; $j<($size & 7); $j++) { + $v = $v << 1; + $v |= $this->data[$p]; + $p++; + } + $data[$bytes] = $v; + } + + return $data; + } + + } + + + + +//---- qrsplit.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * Input splitting classes + * + * Based on libqrencode C library distributed under LGPL 2.1 + * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010 Dominik Dzienia + * + * The following data / specifications are taken from + * "Two dimensional symbol -- QR-code -- Basic Specification" (JIS X0510:2004) + * or + * "Automatic identification and data capture techniques -- + * QR Code 2005 bar code symbology specification" (ISO/IEC 18004:2006) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + class QRsplit { + + public $dataStr = ''; + public $input; + public $modeHint; + + //---------------------------------------------------------------------- + public function __construct($dataStr, $input, $modeHint) + { + $this->dataStr = $dataStr; + $this->input = $input; + $this->modeHint = $modeHint; + } + + //---------------------------------------------------------------------- + public static function isdigitat($str, $pos) + { + if ($pos >= strlen($str)) + return false; + + return ((ord($str[$pos]) >= ord('0'))&&(ord($str[$pos]) <= ord('9'))); + } + + //---------------------------------------------------------------------- + public static function isalnumat($str, $pos) + { + if ($pos >= strlen($str)) + return false; + + return (QRinput::lookAnTable(ord($str[$pos])) >= 0); + } + + //---------------------------------------------------------------------- + public function identifyMode($pos) + { + if ($pos >= strlen($this->dataStr)) + return QR_MODE_NUL; + + $c = $this->dataStr[$pos]; + + if(self::isdigitat($this->dataStr, $pos)) { + return QR_MODE_NUM; + } else if(self::isalnumat($this->dataStr, $pos)) { + return QR_MODE_AN; + } else if($this->modeHint == QR_MODE_KANJI) { + + if ($pos+1 < strlen($this->dataStr)) + { + $d = $this->dataStr[$pos+1]; + $word = (ord($c) << 8) | ord($d); + if(($word >= 0x8140 && $word <= 0x9ffc) || ($word >= 0xe040 && $word <= 0xebbf)) { + return QR_MODE_KANJI; + } + } + } + + return QR_MODE_8; + } + + //---------------------------------------------------------------------- + public function eatNum() + { + $ln = QRspec::lengthIndicator(QR_MODE_NUM, $this->input->getVersion()); + + $p = 0; + while(self::isdigitat($this->dataStr, $p)) { + $p++; + } + + $run = $p; + $mode = $this->identifyMode($p); + + if($mode == QR_MODE_8) { + $dif = QRinput::estimateBitsModeNum($run) + 4 + $ln + + QRinput::estimateBitsMode8(1) // + 4 + l8 + - QRinput::estimateBitsMode8($run + 1); // - 4 - l8 + if($dif > 0) { + return $this->eat8(); + } + } + if($mode == QR_MODE_AN) { + $dif = QRinput::estimateBitsModeNum($run) + 4 + $ln + + QRinput::estimateBitsModeAn(1) // + 4 + la + - QRinput::estimateBitsModeAn($run + 1);// - 4 - la + if($dif > 0) { + return $this->eatAn(); + } + } + + $ret = $this->input->append(QR_MODE_NUM, $run, str_split($this->dataStr)); + if($ret < 0) + return -1; + + return $run; + } + + //---------------------------------------------------------------------- + public function eatAn() + { + $la = QRspec::lengthIndicator(QR_MODE_AN, $this->input->getVersion()); + $ln = QRspec::lengthIndicator(QR_MODE_NUM, $this->input->getVersion()); + + $p = 0; + + while(self::isalnumat($this->dataStr, $p)) { + if(self::isdigitat($this->dataStr, $p)) { + $q = $p; + while(self::isdigitat($this->dataStr, $q)) { + $q++; + } + + $dif = QRinput::estimateBitsModeAn($p) // + 4 + la + + QRinput::estimateBitsModeNum($q - $p) + 4 + $ln + - QRinput::estimateBitsModeAn($q); // - 4 - la + + if($dif < 0) { + break; + } else { + $p = $q; + } + } else { + $p++; + } + } + + $run = $p; + + if(!self::isalnumat($this->dataStr, $p)) { + $dif = QRinput::estimateBitsModeAn($run) + 4 + $la + + QRinput::estimateBitsMode8(1) // + 4 + l8 + - QRinput::estimateBitsMode8($run + 1); // - 4 - l8 + if($dif > 0) { + return $this->eat8(); + } + } + + $ret = $this->input->append(QR_MODE_AN, $run, str_split($this->dataStr)); + if($ret < 0) + return -1; + + return $run; + } + + //---------------------------------------------------------------------- + public function eatKanji() + { + $p = 0; + + while($this->identifyMode($p) == QR_MODE_KANJI) { + $p += 2; + } + + $ret = $this->input->append(QR_MODE_KANJI, $p, str_split($this->dataStr)); + if($ret < 0) + return -1; + + return $run; + } + + //---------------------------------------------------------------------- + public function eat8() + { + $la = QRspec::lengthIndicator(QR_MODE_AN, $this->input->getVersion()); + $ln = QRspec::lengthIndicator(QR_MODE_NUM, $this->input->getVersion()); + + $p = 1; + $dataStrLen = strlen($this->dataStr); + + while($p < $dataStrLen) { + + $mode = $this->identifyMode($p); + if($mode == QR_MODE_KANJI) { + break; + } + if($mode == QR_MODE_NUM) { + $q = $p; + while(self::isdigitat($this->dataStr, $q)) { + $q++; + } + $dif = QRinput::estimateBitsMode8($p) // + 4 + l8 + + QRinput::estimateBitsModeNum($q - $p) + 4 + $ln + - QRinput::estimateBitsMode8($q); // - 4 - l8 + if($dif < 0) { + break; + } else { + $p = $q; + } + } else if($mode == QR_MODE_AN) { + $q = $p; + while(self::isalnumat($this->dataStr, $q)) { + $q++; + } + $dif = QRinput::estimateBitsMode8($p) // + 4 + l8 + + QRinput::estimateBitsModeAn($q - $p) + 4 + $la + - QRinput::estimateBitsMode8($q); // - 4 - l8 + if($dif < 0) { + break; + } else { + $p = $q; + } + } else { + $p++; + } + } + + $run = $p; + $ret = $this->input->append(QR_MODE_8, $run, str_split($this->dataStr)); + + if($ret < 0) + return -1; + + return $run; + } + + //---------------------------------------------------------------------- + public function splitString() + { + while (strlen($this->dataStr) > 0) + { + if($this->dataStr == '') + return 0; + + $mode = $this->identifyMode(0); + + switch ($mode) { + case QR_MODE_NUM: $length = $this->eatNum(); break; + case QR_MODE_AN: $length = $this->eatAn(); break; + case QR_MODE_KANJI: + if ($hint == QR_MODE_KANJI) + $length = $this->eatKanji(); + else $length = $this->eat8(); + break; + default: $length = $this->eat8(); break; + + } + + if($length == 0) return 0; + if($length < 0) return -1; + + $this->dataStr = substr($this->dataStr, $length); + } + } + + //---------------------------------------------------------------------- + public function toUpper() + { + $stringLen = strlen($this->dataStr); + $p = 0; + + while ($p<$stringLen) { + $mode = self::identifyMode(substr($this->dataStr, $p), $this->modeHint); + if($mode == QR_MODE_KANJI) { + $p += 2; + } else { + if (ord($this->dataStr[$p]) >= ord('a') && ord($this->dataStr[$p]) <= ord('z')) { + $this->dataStr[$p] = chr(ord($this->dataStr[$p]) - 32); + } + $p++; + } + } + + return $this->dataStr; + } + + //---------------------------------------------------------------------- + public static function splitStringToQRinput($string, QRinput $input, $modeHint, $casesensitive = true) + { + if(is_null($string) || $string == '\0' || $string == '') { + throw new Exception('empty string!!!'); + } + + $split = new QRsplit($string, $input, $modeHint); + + if(!$casesensitive) + $split->toUpper(); + + return $split->splitString(); + } + } + + + +//---- qrrscode.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * Reed-Solomon error correction support + * + * Copyright (C) 2002, 2003, 2004, 2006 Phil Karn, KA9Q + * (libfec is released under the GNU Lesser General Public License.) + * + * Based on libqrencode C library distributed under LGPL 2.1 + * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010 Dominik Dzienia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + class QRrsItem { + + public $mm; // Bits per symbol + public $nn; // Symbols per block (= (1<= $this->nn) { + $x -= $this->nn; + $x = ($x >> $this->mm) + ($x & $this->nn); + } + + return $x; + } + + //---------------------------------------------------------------------- + public static function init_rs_char($symsize, $gfpoly, $fcr, $prim, $nroots, $pad) + { + // Common code for intializing a Reed-Solomon control block (char or int symbols) + // Copyright 2004 Phil Karn, KA9Q + // May be used under the terms of the GNU Lesser General Public License (LGPL) + + $rs = null; + + // Check parameter ranges + if($symsize < 0 || $symsize > 8) return $rs; + if($fcr < 0 || $fcr >= (1<<$symsize)) return $rs; + if($prim <= 0 || $prim >= (1<<$symsize)) return $rs; + if($nroots < 0 || $nroots >= (1<<$symsize)) return $rs; // Can't have more roots than symbol values! + if($pad < 0 || $pad >= ((1<<$symsize) -1 - $nroots)) return $rs; // Too much padding + + $rs = new QRrsItem(); + $rs->mm = $symsize; + $rs->nn = (1<<$symsize)-1; + $rs->pad = $pad; + + $rs->alpha_to = array_fill(0, $rs->nn+1, 0); + $rs->index_of = array_fill(0, $rs->nn+1, 0); + + // PHP style macro replacement ;) + $NN =& $rs->nn; + $A0 =& $NN; + + // Generate Galois field lookup tables + $rs->index_of[0] = $A0; // log(zero) = -inf + $rs->alpha_to[$A0] = 0; // alpha**-inf = 0 + $sr = 1; + + for($i=0; $i<$rs->nn; $i++) { + $rs->index_of[$sr] = $i; + $rs->alpha_to[$i] = $sr; + $sr <<= 1; + if($sr & (1<<$symsize)) { + $sr ^= $gfpoly; + } + $sr &= $rs->nn; + } + + if($sr != 1){ + // field generator polynomial is not primitive! + $rs = NULL; + return $rs; + } + + /* Form RS code generator polynomial from its roots */ + $rs->genpoly = array_fill(0, $nroots+1, 0); + + $rs->fcr = $fcr; + $rs->prim = $prim; + $rs->nroots = $nroots; + $rs->gfpoly = $gfpoly; + + /* Find prim-th root of 1, used in decoding */ + for($iprim=1;($iprim % $prim) != 0;$iprim += $rs->nn) + ; // intentional empty-body loop! + + $rs->iprim = (int)($iprim / $prim); + $rs->genpoly[0] = 1; + + for ($i = 0,$root=$fcr*$prim; $i < $nroots; $i++, $root += $prim) { + $rs->genpoly[$i+1] = 1; + + // Multiply rs->genpoly[] by @**(root + x) + for ($j = $i; $j > 0; $j--) { + if ($rs->genpoly[$j] != 0) { + $rs->genpoly[$j] = $rs->genpoly[$j-1] ^ $rs->alpha_to[$rs->modnn($rs->index_of[$rs->genpoly[$j]] + $root)]; + } else { + $rs->genpoly[$j] = $rs->genpoly[$j-1]; + } + } + // rs->genpoly[0] can never be zero + $rs->genpoly[0] = $rs->alpha_to[$rs->modnn($rs->index_of[$rs->genpoly[0]] + $root)]; + } + + // convert rs->genpoly[] to index form for quicker encoding + for ($i = 0; $i <= $nroots; $i++) + $rs->genpoly[$i] = $rs->index_of[$rs->genpoly[$i]]; + + return $rs; + } + + //---------------------------------------------------------------------- + public function encode_rs_char($data, &$parity) + { + $MM =& $this->mm; + $NN =& $this->nn; + $ALPHA_TO =& $this->alpha_to; + $INDEX_OF =& $this->index_of; + $GENPOLY =& $this->genpoly; + $NROOTS =& $this->nroots; + $FCR =& $this->fcr; + $PRIM =& $this->prim; + $IPRIM =& $this->iprim; + $PAD =& $this->pad; + $A0 =& $NN; + + $parity = array_fill(0, $NROOTS, 0); + + for($i=0; $i< ($NN-$NROOTS-$PAD); $i++) { + + $feedback = $INDEX_OF[$data[$i] ^ $parity[0]]; + if($feedback != $A0) { + // feedback term is non-zero + + // This line is unnecessary when GENPOLY[NROOTS] is unity, as it must + // always be for the polynomials constructed by init_rs() + $feedback = $this->modnn($NN - $GENPOLY[$NROOTS] + $feedback); + + for($j=1;$j<$NROOTS;$j++) { + $parity[$j] ^= $ALPHA_TO[$this->modnn($feedback + $GENPOLY[$NROOTS-$j])]; + } + } + + // Shift + array_shift($parity); + if($feedback != $A0) { + array_push($parity, $ALPHA_TO[$this->modnn($feedback + $GENPOLY[0])]); + } else { + array_push($parity, 0); + } + } + } + } + + //########################################################################## + + class QRrs { + + public static $items = array(); + + //---------------------------------------------------------------------- + public static function init_rs($symsize, $gfpoly, $fcr, $prim, $nroots, $pad) + { + foreach(self::$items as $rs) { + if($rs->pad != $pad) continue; + if($rs->nroots != $nroots) continue; + if($rs->mm != $symsize) continue; + if($rs->gfpoly != $gfpoly) continue; + if($rs->fcr != $fcr) continue; + if($rs->prim != $prim) continue; + + return $rs; + } + + $rs = QRrsItem::init_rs_char($symsize, $gfpoly, $fcr, $prim, $nroots, $pad); + array_unshift(self::$items, $rs); + + return $rs; + } + } + + + +//---- qrmask.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * Masking + * + * Based on libqrencode C library distributed under LGPL 2.1 + * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010 Dominik Dzienia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + define('N1', 3); + define('N2', 3); + define('N3', 40); + define('N4', 10); + + class QRmask { + + public $runLength = array(); + + //---------------------------------------------------------------------- + public function __construct() + { + $this->runLength = array_fill(0, QRSPEC_WIDTH_MAX + 1, 0); + } + + //---------------------------------------------------------------------- + public function writeFormatInformation($width, &$frame, $mask, $level) + { + $blacks = 0; + $format = QRspec::getFormatInfo($mask, $level); + + for($i=0; $i<8; $i++) { + if($format & 1) { + $blacks += 2; + $v = 0x85; + } else { + $v = 0x84; + } + + $frame[8][$width - 1 - $i] = chr($v); + if($i < 6) { + $frame[$i][8] = chr($v); + } else { + $frame[$i + 1][8] = chr($v); + } + $format = $format >> 1; + } + + for($i=0; $i<7; $i++) { + if($format & 1) { + $blacks += 2; + $v = 0x85; + } else { + $v = 0x84; + } + + $frame[$width - 7 + $i][8] = chr($v); + if($i == 0) { + $frame[8][7] = chr($v); + } else { + $frame[8][6 - $i] = chr($v); + } + + $format = $format >> 1; + } + + return $blacks; + } + + //---------------------------------------------------------------------- + public function mask0($x, $y) { return ($x+$y)&1; } + public function mask1($x, $y) { return ($y&1); } + public function mask2($x, $y) { return ($x%3); } + public function mask3($x, $y) { return ($x+$y)%3; } + public function mask4($x, $y) { return (((int)($y/2))+((int)($x/3)))&1; } + public function mask5($x, $y) { return (($x*$y)&1)+($x*$y)%3; } + public function mask6($x, $y) { return ((($x*$y)&1)+($x*$y)%3)&1; } + public function mask7($x, $y) { return ((($x*$y)%3)+(($x+$y)&1))&1; } + + //---------------------------------------------------------------------- + private function generateMaskNo($maskNo, $width, $frame) + { + $bitMask = array_fill(0, $width, array_fill(0, $width, 0)); + + for($y=0; $y<$width; $y++) { + for($x=0; $x<$width; $x++) { + if(ord($frame[$y][$x]) & 0x80) { + $bitMask[$y][$x] = 0; + } else { + $maskFunc = call_user_func(array($this, 'mask'.$maskNo), $x, $y); + $bitMask[$y][$x] = ($maskFunc == 0)?1:0; + } + + } + } + + return $bitMask; + } + + //---------------------------------------------------------------------- + public static function serial($bitFrame) + { + $codeArr = array(); + + foreach ($bitFrame as $line) + $codeArr[] = join('', $line); + + return gzcompress(join("\n", $codeArr), 9); + } + + //---------------------------------------------------------------------- + public static function unserial($code) + { + $codeArr = array(); + + $codeLines = explode("\n", gzuncompress($code)); + foreach ($codeLines as $line) + $codeArr[] = str_split($line); + + return $codeArr; + } + + //---------------------------------------------------------------------- + public function makeMaskNo($maskNo, $width, $s, &$d, $maskGenOnly = false) + { + $b = 0; + $bitMask = array(); + + $fileName = QR_CACHE_DIR.'mask_'.$maskNo.DIRECTORY_SEPARATOR.'mask_'.$width.'_'.$maskNo.'.dat'; + + if (QR_CACHEABLE) { + if (file_exists($fileName)) { + $bitMask = self::unserial(file_get_contents($fileName)); + } else { + $bitMask = $this->generateMaskNo($maskNo, $width, $s, $d); + if (!file_exists(QR_CACHE_DIR.'mask_'.$maskNo)) + mkdir(QR_CACHE_DIR.'mask_'.$maskNo); + file_put_contents($fileName, self::serial($bitMask)); + } + } else { + $bitMask = $this->generateMaskNo($maskNo, $width, $s, $d); + } + + if ($maskGenOnly) + return; + + $d = $s; + + for($y=0; $y<$width; $y++) { + for($x=0; $x<$width; $x++) { + if($bitMask[$y][$x] == 1) { + $d[$y][$x] = chr(ord($s[$y][$x]) ^ (int)$bitMask[$y][$x]); + } + $b += (int)(ord($d[$y][$x]) & 1); + } + } + + return $b; + } + + //---------------------------------------------------------------------- + public function makeMask($width, $frame, $maskNo, $level) + { + $masked = array_fill(0, $width, str_repeat("\0", $width)); + $this->makeMaskNo($maskNo, $width, $frame, $masked); + $this->writeFormatInformation($width, $masked, $maskNo, $level); + + return $masked; + } + + //---------------------------------------------------------------------- + public function calcN1N3($length) + { + $demerit = 0; + + for($i=0; $i<$length; $i++) { + + if($this->runLength[$i] >= 5) { + $demerit += (N1 + ($this->runLength[$i] - 5)); + } + if($i & 1) { + if(($i >= 3) && ($i < ($length-2)) && ($this->runLength[$i] % 3 == 0)) { + $fact = (int)($this->runLength[$i] / 3); + if(($this->runLength[$i-2] == $fact) && + ($this->runLength[$i-1] == $fact) && + ($this->runLength[$i+1] == $fact) && + ($this->runLength[$i+2] == $fact)) { + if(($this->runLength[$i-3] < 0) || ($this->runLength[$i-3] >= (4 * $fact))) { + $demerit += N3; + } else if((($i+3) >= $length) || ($this->runLength[$i+3] >= (4 * $fact))) { + $demerit += N3; + } + } + } + } + } + return $demerit; + } + + //---------------------------------------------------------------------- + public function evaluateSymbol($width, $frame) + { + $head = 0; + $demerit = 0; + + for($y=0; $y<$width; $y++) { + $head = 0; + $this->runLength[0] = 1; + + $frameY = $frame[$y]; + + if ($y>0) + $frameYM = $frame[$y-1]; + + for($x=0; $x<$width; $x++) { + if(($x > 0) && ($y > 0)) { + $b22 = ord($frameY[$x]) & ord($frameY[$x-1]) & ord($frameYM[$x]) & ord($frameYM[$x-1]); + $w22 = ord($frameY[$x]) | ord($frameY[$x-1]) | ord($frameYM[$x]) | ord($frameYM[$x-1]); + + if(($b22 | ($w22 ^ 1))&1) { + $demerit += N2; + } + } + if(($x == 0) && (ord($frameY[$x]) & 1)) { + $this->runLength[0] = -1; + $head = 1; + $this->runLength[$head] = 1; + } else if($x > 0) { + if((ord($frameY[$x]) ^ ord($frameY[$x-1])) & 1) { + $head++; + $this->runLength[$head] = 1; + } else { + $this->runLength[$head]++; + } + } + } + + $demerit += $this->calcN1N3($head+1); + } + + for($x=0; $x<$width; $x++) { + $head = 0; + $this->runLength[0] = 1; + + for($y=0; $y<$width; $y++) { + if($y == 0 && (ord($frame[$y][$x]) & 1)) { + $this->runLength[0] = -1; + $head = 1; + $this->runLength[$head] = 1; + } else if($y > 0) { + if((ord($frame[$y][$x]) ^ ord($frame[$y-1][$x])) & 1) { + $head++; + $this->runLength[$head] = 1; + } else { + $this->runLength[$head]++; + } + } + } + + $demerit += $this->calcN1N3($head+1); + } + + return $demerit; + } + + + //---------------------------------------------------------------------- + public function mask($width, $frame, $level) + { + $minDemerit = PHP_INT_MAX; + $bestMaskNum = 0; + $bestMask = array(); + + $checked_masks = array(0,1,2,3,4,5,6,7); + + if (QR_FIND_FROM_RANDOM !== false) { + + $howManuOut = 8-(QR_FIND_FROM_RANDOM % 9); + for ($i = 0; $i < $howManuOut; $i++) { + $remPos = rand (0, count($checked_masks)-1); + unset($checked_masks[$remPos]); + $checked_masks = array_values($checked_masks); + } + + } + + $bestMask = $frame; + + foreach($checked_masks as $i) { + $mask = array_fill(0, $width, str_repeat("\0", $width)); + + $demerit = 0; + $blacks = 0; + $blacks = $this->makeMaskNo($i, $width, $frame, $mask); + $blacks += $this->writeFormatInformation($width, $mask, $i, $level); + $blacks = (int)(100 * $blacks / ($width * $width)); + $demerit = (int)((int)(abs($blacks - 50) / 5) * N4); + $demerit += $this->evaluateSymbol($width, $mask); + + if($demerit < $minDemerit) { + $minDemerit = $demerit; + $bestMask = $mask; + $bestMaskNum = $i; + } + } + + return $bestMask; + } + + //---------------------------------------------------------------------- + } + + + + +//---- qrencode.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * Main encoder classes. + * + * Based on libqrencode C library distributed under LGPL 2.1 + * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010 Dominik Dzienia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + class QRrsblock { + public $dataLength; + public $data = array(); + public $eccLength; + public $ecc = array(); + + public function __construct($dl, $data, $el, &$ecc, QRrsItem $rs) + { + $rs->encode_rs_char($data, $ecc); + + $this->dataLength = $dl; + $this->data = $data; + $this->eccLength = $el; + $this->ecc = $ecc; + } + }; + + //########################################################################## + + class QRrawcode { + public $version; + public $datacode = array(); + public $ecccode = array(); + public $blocks; + public $rsblocks = array(); //of RSblock + public $count; + public $dataLength; + public $eccLength; + public $b1; + + //---------------------------------------------------------------------- + public function __construct(QRinput $input) + { + $spec = array(0,0,0,0,0); + + $this->datacode = $input->getByteStream(); + if(is_null($this->datacode)) { + throw new Exception('null imput string'); + } + + QRspec::getEccSpec($input->getVersion(), $input->getErrorCorrectionLevel(), $spec); + + $this->version = $input->getVersion(); + $this->b1 = QRspec::rsBlockNum1($spec); + $this->dataLength = QRspec::rsDataLength($spec); + $this->eccLength = QRspec::rsEccLength($spec); + $this->ecccode = array_fill(0, $this->eccLength, 0); + $this->blocks = QRspec::rsBlockNum($spec); + + $ret = $this->init($spec); + if($ret < 0) { + throw new Exception('block alloc error'); + return null; + } + + $this->count = 0; + } + + //---------------------------------------------------------------------- + public function init(array $spec) + { + $dl = QRspec::rsDataCodes1($spec); + $el = QRspec::rsEccCodes1($spec); + $rs = QRrs::init_rs(8, 0x11d, 0, 1, $el, 255 - $dl - $el); + + + $blockNo = 0; + $dataPos = 0; + $eccPos = 0; + for($i=0; $iecccode,$eccPos); + $this->rsblocks[$blockNo] = new QRrsblock($dl, array_slice($this->datacode, $dataPos), $el, $ecc, $rs); + $this->ecccode = array_merge(array_slice($this->ecccode,0, $eccPos), $ecc); + + $dataPos += $dl; + $eccPos += $el; + $blockNo++; + } + + if(QRspec::rsBlockNum2($spec) == 0) + return 0; + + $dl = QRspec::rsDataCodes2($spec); + $el = QRspec::rsEccCodes2($spec); + $rs = QRrs::init_rs(8, 0x11d, 0, 1, $el, 255 - $dl - $el); + + if($rs == NULL) return -1; + + for($i=0; $iecccode,$eccPos); + $this->rsblocks[$blockNo] = new QRrsblock($dl, array_slice($this->datacode, $dataPos), $el, $ecc, $rs); + $this->ecccode = array_merge(array_slice($this->ecccode,0, $eccPos), $ecc); + + $dataPos += $dl; + $eccPos += $el; + $blockNo++; + } + + return 0; + } + + //---------------------------------------------------------------------- + public function getCode() + { + $ret; + + if($this->count < $this->dataLength) { + $row = $this->count % $this->blocks; + $col = $this->count / $this->blocks; + if($col >= $this->rsblocks[0]->dataLength) { + $row += $this->b1; + } + $ret = $this->rsblocks[$row]->data[$col]; + } else if($this->count < $this->dataLength + $this->eccLength) { + $row = ($this->count - $this->dataLength) % $this->blocks; + $col = ($this->count - $this->dataLength) / $this->blocks; + $ret = $this->rsblocks[$row]->ecc[$col]; + } else { + return 0; + } + $this->count++; + + return $ret; + } + } + + //########################################################################## + + class QRcode { + + public $version; + public $width; + public $data; + + //---------------------------------------------------------------------- + public function encodeMask(QRinput $input, $mask) + { + if($input->getVersion() < 0 || $input->getVersion() > QRSPEC_VERSION_MAX) { + throw new Exception('wrong version'); + } + if($input->getErrorCorrectionLevel() > QR_ECLEVEL_H) { + throw new Exception('wrong level'); + } + + $raw = new QRrawcode($input); + + QRtools::markTime('after_raw'); + + $version = $raw->version; + $width = QRspec::getWidth($version); + $frame = QRspec::newFrame($version); + + $filler = new FrameFiller($width, $frame); + if(is_null($filler)) { + return NULL; + } + + // inteleaved data and ecc codes + for($i=0; $i<$raw->dataLength + $raw->eccLength; $i++) { + $code = $raw->getCode(); + $bit = 0x80; + for($j=0; $j<8; $j++) { + $addr = $filler->next(); + $filler->setFrameAt($addr, 0x02 | (($bit & $code) != 0)); + $bit = $bit >> 1; + } + } + + QRtools::markTime('after_filler'); + + unset($raw); + + // remainder bits + $j = QRspec::getRemainder($version); + for($i=0; $i<$j; $i++) { + $addr = $filler->next(); + $filler->setFrameAt($addr, 0x02); + } + + $frame = $filler->frame; + unset($filler); + + + // masking + $maskObj = new QRmask(); + if($mask < 0) { + + if (QR_FIND_BEST_MASK) { + $masked = $maskObj->mask($width, $frame, $input->getErrorCorrectionLevel()); + } else { + $masked = $maskObj->makeMask($width, $frame, (intval(QR_DEFAULT_MASK) % 8), $input->getErrorCorrectionLevel()); + } + } else { + $masked = $maskObj->makeMask($width, $frame, $mask, $input->getErrorCorrectionLevel()); + } + + if($masked == NULL) { + return NULL; + } + + QRtools::markTime('after_mask'); + + $this->version = $version; + $this->width = $width; + $this->data = $masked; + + return $this; + } + + //---------------------------------------------------------------------- + public function encodeInput(QRinput $input) + { + return $this->encodeMask($input, -1); + } + + //---------------------------------------------------------------------- + public function encodeString8bit($string, $version, $level) + { + if(string == NULL) { + throw new Exception('empty string!'); + return NULL; + } + + $input = new QRinput($version, $level); + if($input == NULL) return NULL; + + $ret = $input->append($input, QR_MODE_8, strlen($string), str_split($string)); + if($ret < 0) { + unset($input); + return NULL; + } + return $this->encodeInput($input); + } + + //---------------------------------------------------------------------- + public function encodeString($string, $version, $level, $hint, $casesensitive) + { + + if($hint != QR_MODE_8 && $hint != QR_MODE_KANJI) { + throw new Exception('bad hint'); + return NULL; + } + + $input = new QRinput($version, $level); + if($input == NULL) return NULL; + + $ret = QRsplit::splitStringToQRinput($string, $input, $hint, $casesensitive); + if($ret < 0) { + return NULL; + } + + return $this->encodeInput($input); + } + + //---------------------------------------------------------------------- + public static function png($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4, $saveandprint=false) + { + $enc = QRencode::factory($level, $size, $margin); + return $enc->encodePNG($text, $outfile, $saveandprint=false); + } + + //---------------------------------------------------------------------- + public static function text($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4) + { + $enc = QRencode::factory($level, $size, $margin); + return $enc->encode($text, $outfile); + } + + //---------------------------------------------------------------------- + public static function raw($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4) + { + $enc = QRencode::factory($level, $size, $margin); + return $enc->encodeRAW($text, $outfile); + } + } + + //########################################################################## + + class FrameFiller { + + public $width; + public $frame; + public $x; + public $y; + public $dir; + public $bit; + + //---------------------------------------------------------------------- + public function __construct($width, &$frame) + { + $this->width = $width; + $this->frame = $frame; + $this->x = $width - 1; + $this->y = $width - 1; + $this->dir = -1; + $this->bit = -1; + } + + //---------------------------------------------------------------------- + public function setFrameAt($at, $val) + { + $this->frame[$at['y']][$at['x']] = chr($val); + } + + //---------------------------------------------------------------------- + public function getFrameAt($at) + { + return ord($this->frame[$at['y']][$at['x']]); + } + + //---------------------------------------------------------------------- + public function next() + { + do { + + if($this->bit == -1) { + $this->bit = 0; + return array('x'=>$this->x, 'y'=>$this->y); + } + + $x = $this->x; + $y = $this->y; + $w = $this->width; + + if($this->bit == 0) { + $x--; + $this->bit++; + } else { + $x++; + $y += $this->dir; + $this->bit--; + } + + if($this->dir < 0) { + if($y < 0) { + $y = 0; + $x -= 2; + $this->dir = 1; + if($x == 6) { + $x--; + $y = 9; + } + } + } else { + if($y == $w) { + $y = $w - 1; + $x -= 2; + $this->dir = -1; + if($x == 6) { + $x--; + $y -= 8; + } + } + } + if($x < 0 || $y < 0) return null; + + $this->x = $x; + $this->y = $y; + + } while(ord($this->frame[$y][$x]) & 0x80); + + return array('x'=>$x, 'y'=>$y); + } + + } ; + + //########################################################################## + + class QRencode { + + public $casesensitive = true; + public $eightbit = false; + + public $version = 0; + public $size = 3; + public $margin = 4; + + public $structured = 0; // not supported yet + + public $level = QR_ECLEVEL_L; + public $hint = QR_MODE_8; + + //---------------------------------------------------------------------- + public static function factory($level = QR_ECLEVEL_L, $size = 3, $margin = 4) + { + $enc = new QRencode(); + $enc->size = $size; + $enc->margin = $margin; + + switch ($level.'') { + case '0': + case '1': + case '2': + case '3': + $enc->level = $level; + break; + case 'l': + case 'L': + $enc->level = QR_ECLEVEL_L; + break; + case 'm': + case 'M': + $enc->level = QR_ECLEVEL_M; + break; + case 'q': + case 'Q': + $enc->level = QR_ECLEVEL_Q; + break; + case 'h': + case 'H': + $enc->level = QR_ECLEVEL_H; + break; + } + + return $enc; + } + + //---------------------------------------------------------------------- + public function encodeRAW($intext, $outfile = false) + { + $code = new QRcode(); + + if($this->eightbit) { + $code->encodeString8bit($intext, $this->version, $this->level); + } else { + $code->encodeString($intext, $this->version, $this->level, $this->hint, $this->casesensitive); + } + + return $code->data; + } + + //---------------------------------------------------------------------- + public function encode($intext, $outfile = false) + { + $code = new QRcode(); + + if($this->eightbit) { + $code->encodeString8bit($intext, $this->version, $this->level); + } else { + $code->encodeString($intext, $this->version, $this->level, $this->hint, $this->casesensitive); + } + + QRtools::markTime('after_encode'); + + if ($outfile!== false) { + file_put_contents($outfile, join("\n", QRtools::binarize($code->data))); + } else { + return QRtools::binarize($code->data); + } + } + + //---------------------------------------------------------------------- + public function encodePNG($intext, $outfile = false,$saveandprint=false) + { + try { + + ob_start(); + $tab = $this->encode($intext); + $err = ob_get_contents(); + ob_end_clean(); + + if ($err != '') + QRtools::log($outfile, $err); + + $maxSize = (int)(QR_PNG_MAXIMUM_SIZE / (count($tab)+2*$this->margin)); + + QRimage::png($tab, $outfile, min(max(1, $this->size), $maxSize), $this->margin,$saveandprint); + + } catch (Exception $e) { + + QRtools::log($outfile, $e->getMessage()); + + } + } + } + + diff --git a/weixinpay/example/qrcode.php b/weixinpay/example/qrcode.php new file mode 100644 index 0000000..7acf937 --- /dev/null +++ b/weixinpay/example/qrcode.php @@ -0,0 +1,5 @@ + + + + + 微信支付样例-退款 + +$value){ + echo "$key : $value
    "; + } +} + +if(isset($_REQUEST["transaction_id"]) && $_REQUEST["transaction_id"] != ""){ + $transaction_id = $_REQUEST["transaction_id"]; + $total_fee = $_REQUEST["total_fee"]; + $refund_fee = $_REQUEST["refund_fee"]; + $input = new WxPayRefund(); + $input->SetTransaction_id($transaction_id); + $input->SetTotal_fee($total_fee); + $input->SetRefund_fee($refund_fee); + $input->SetOut_refund_no(WX_MCHID.date("YmdHis")); + $input->SetOp_user_id(WX_MCHID); + printf_info(WxPayApi::refund($input)); + exit(); +} + +//$_REQUEST["out_trade_no"]= "122531270220150304194108"; +///$_REQUEST["total_fee"]= "1"; +//$_REQUEST["refund_fee"] = "1"; +if(isset($_REQUEST["out_trade_no"]) && $_REQUEST["out_trade_no"] != ""){ + $out_trade_no = $_REQUEST["out_trade_no"]; + $total_fee = $_REQUEST["total_fee"]; + $refund_fee = $_REQUEST["refund_fee"]; + $input = new WxPayRefund(); + $input->SetOut_trade_no($out_trade_no); + $input->SetTotal_fee($total_fee); + $input->SetRefund_fee($refund_fee); + $input->SetOut_refund_no(WX_MCHID.date("YmdHis")); + $input->SetOp_user_id(WX_MCHID); + printf_info(WxPayApi::refund($input)); + exit(); +} +?> + +
    +
    微信订单号和商户订单号选少填一个,微信订单号优先:

    +
    微信订单号:

    +

    +
    商户订单号:

    +

    +
    订单总金额(分):

    +

    +
    退款金额(分):

    +

    +
    + +
    +
    + + \ No newline at end of file diff --git a/weixinpay/example/refundquery.php b/weixinpay/example/refundquery.php new file mode 100644 index 0000000..df003cc --- /dev/null +++ b/weixinpay/example/refundquery.php @@ -0,0 +1,73 @@ + + + + + 微信支付样例-查退款单 + +$value){ + echo "$key : $value
    "; + } +} + +if(isset($_REQUEST["transaction_id"]) && $_REQUEST["transaction_id"] != ""){ + $transaction_id = $_REQUEST["transaction_id"]; + $input = new WxPayRefundQuery(); + $input->SetTransaction_id($transaction_id); + printf_info(WxPayApi::refundQuery($input)); +} + +if(isset($_REQUEST["out_trade_no"]) && $_REQUEST["out_trade_no"] != ""){ + $out_trade_no = $_REQUEST["out_trade_no"]; + $input = new WxPayRefundQuery(); + $input->SetOut_trade_no($out_trade_no); + printf_info(WxPayApi::refundQuery($input)); + exit(); +} + +if(isset($_REQUEST["out_refund_no"]) && $_REQUEST["out_refund_no"] != ""){ + $out_refund_no = $_REQUEST["out_refund_no"]; + $input = new WxPayRefundQuery(); + $input->SetOut_refund_no($out_refund_no); + printf_info(WxPayApi::refundQuery($input)); + exit(); +} + +if(isset($_REQUEST["refund_id"]) && $_REQUEST["refund_id"] != ""){ + $refund_id = $_REQUEST["refund_id"]; + $input = new WxPayRefundQuery(); + $input->SetRefund_id($refund_id); + printf_info(WxPayApi::refundQuery($input)); + exit(); +} + +?> + +
    +
    微信订单号、商户订单号、微信订单号、微信退款单号选填至少一个,微信退款单号优先:

    +
    微信订单号:

    +

    +
    商户订单号:

    +

    +
    商户退款单号:

    +

    +
    微信退款单号:

    +

    +
    + +
    +
    + + \ No newline at end of file diff --git a/weixinpay/image/bk.png b/weixinpay/image/bk.png new file mode 100644 index 0000000000000000000000000000000000000000..96125e25f77902d4f8b55e518e78fad012a5dd30 GIT binary patch literal 1505 zcmeAS@N?(olHy`uVBq!ia0y~yV98}*U}WH60*bW7$!rEvY)RhkE)4%caKYZ?lYt_f z1s;*b3=DinK$vl=HlH*D18bwFi(^Q|oVV8?r6T){`qz_ zP+vjrUOpi8LsFiVf#Jdr4F-mWWA2O$3?_C!2Q2s)2$bvX2ilxbBf`Mo@OUwhFEy%k mGz3ONU^E0qLx4~S@YyM|Ok?+W3oMKo7(8A5T-G@yGywqGkYxe@ literal 0 HcmV?d00001 diff --git a/weixinpay/image/image001.jpg b/weixinpay/image/image001.jpg new file mode 100644 index 0000000000000000000000000000000000000000..56ce7da1e4f9c2b498849b3a538bddbcbbb375ff GIT binary patch literal 27022 zcmb5VWmFtNxGg%kLvRQXWQO1bcMnc**Wd&hU~u;kATTh41sj69y9NlZ3C=L+Ai*s_ z2$7p}-dpRgci%bd-rHTjy1&)c-K%QXxA*?)@9N)ez!ObX4OIXJ1_r?P-wF7;4tNP5 z#KR-N!zCoZCmf*!>3oE^vlWaH$M6yW3*<6#2|DGP~7%PJ@+ zJQq;aQjyb?lvj}bI{+ZU$56ne$G{*5{2c}`0{|E}|GM~(UjE+$0}~4y2Nw^YfRN~4 zK-&`lCI%K3CN>rh4mS3`=0Df@E{PA+Z{Q894|NvW60DynMg8k&Yi#wMm_<`xc)PR=fGT-|*A`~w1m zfK^($JTf{qJ~27Ju(-6m zvbu)c*+uW|9~^!=I=;HT`F?wM|Ks83UnBtkKfXsEaOc(#7)qV?ewIV1Qw3|KN?rj2 z^pntbi8onEw@sz7eIPJ!R`6lz<4b^13>gdbd#k<*)&EVS&f`bi=f+)I{c5bWdBv*E zalkAc9zr(ob6N8T-YaV%2ELnMeci(sGg|={4ILIo_Q@&~s$Hy9oO5k*Nu0(1SFO0q z+y-eJ-9Bj)Cpk*y=^Y9CY2p$O_P5lQC~uLPk-KBfUVxmf-^jnyT>Za$^1s(8?*6cj zoD#4ep=z`V9{a+)Z;p7nyuadi*O~kE%-lNLA%+Y)_2@W~G;=1X!LX}iS((CS%Oc9a zQ7bSxCNE**hI4E~{)Ob+^H$)}MadVB7nq zLLq$90w|rYr|RRxNnO}X@6wYg$bbuV0l8M!T4igZUkH3b__J|f*7g)sz@X^Mk*8&& z=}yGU=ahbLHMn{>9Kf$f+LIcbc3%rIFa#^lTfI(vy6L1sI8NUmM!6P1Id(vu`5RLD!u-Dnv+6kNF8Lp)cd<>Y22d5hHY4hcKCIMWPMujZ$3J=tOE`LLH3 zhb$#W1p0l(DpUwfHhG;8raC(07xTuW1ipFVkqyit4mI-l4DErE*+?1p2@k7mciuR~ z+)uzc9o$t$@BFb}@T&TR>=}3Vs(5kw@SWpjT>)cgW6m9JPbU?BLUIc=7E2e=ovwVK z_UnqEr~tzr*U_Nod16((AzvCT=^YI{7YK~gv14#il!(88!#a~^90^A?A%2hhN!tBp zoIQn--^2sPt}8u%X+JyRIt%;kZ0SE-hWn+QCXHXXCqN8=7iNOUtS+zfoFDV@1(+aB zf+i_PePq={^?S9!471NWx>Hz#fj9J~q~k(L+lLjBM?bDi`9vi=19{>*avTNe%6NJL zuxxG9f>V*TA6+-P}_>Z>pXC z2oz>X!iEVepfi#|I+~6#IyGz=H#_pcP2VyvGxxRRtLpBBMq|%tJccHy;>lbkbx*a`M%kia zmwTvL1$P|3#_z>g2-BebN76kqee-^n#apc)yKWw)mij^ntFFkq%`!49>=5r@K%2^T zfzHF}MV43FR;v)FF>hYuntg;l-pP?Qx10E@Z6>xWMVY1`Z+VVtgTC*%1wC-P&Y;pD zTV6sOgS^b~<1z2Oa8%%-w1E2cP%229vT-6MfjzvLfSUb(g=ItbOxDCbSWGeI^jKqc zw{w|O)q0Kxy9UsNJK#=IROw!Qp`t&_^8Eu1xXa1aaL?*$kde4>Im0zX>*&&K&WE3G zIC@v(IW}Whk*Z+k8ML4Z=~-w;{rj@LLy-4%_fERYbhYIhL)cfvjk&2Ysu_wc-4CT% za<=bvw_GV6ioQSzY!xNP#|*VXcg_eLla7x!k>r$FQ+CFQ*uI5zx+L{!aC6l9?+AfI>>Pkkx6xjCA=0XVrjg1>jBP;{~%^?{_XyCgYdJ^979yk_&=o#|KE z7P|fG$qT|CPQW6N4;gi#Ty~l0FB799)AsMEKi5Oq7cb}>M+EoXD_go4yo8}y#8RbYM9hSLUoh!|C z?XF6CXb4R@WPI$^V&UpjpOGB2(33fyp00#DM(75fhY`CUhC5Enlsm|j7BJEe z-ru)+@{&TsE_&-ri1ATL1}MhmU@ApyPBtX z3HD1e%2}`1jtB?p8#P-5!6onTq7jglXMOHEJhF0p`mJ7k?Ns6rm`L8&MAm-r+9Bvt z&CoPYp@|>?_=ZvaxvHu>@a-xc))>*KV5%E~ie&~-<%X3FHUt!!km^AC7tljgI3~-r z5<;E1Ki=gSbZ)fooTOA+K?ib3PV)}h3&&AFFs5+8ofbvOK~Z>Kt^Q*=n+Ns$`6P!( z7lip_{%~pv>^A_5qk-f3$0Zrr@)I1y-LfQz^B?>x+IG$2hJ>q$oXIOS&YvH2Hg~97 zI1vu2Rh%3>guAPL3^jt^F%(;dsek+E_nDgGFF=u6_J2hb{a0C{xg06i#)Ej+P`To2 zS1A=liW~S8c)iPO`9{VO9^%L3j#&96^D||C9XaD9I+5}h;Eg7WPcC1GAKl86_ zkFPK5i%t%Rc7*E7#K^I3KCx$?!y{Oc)q$9f#`0)aCh|W~3`i)ke}eV`G|e)|{-7_O zQ|W6{3fW>rR4wa6{bAs4NT#%vm=sh;(A+}z zok}fa$}8B_JJwC``XviKb%bKizDEPcFYdbfnC#rp^`oCzIpRNomXlvqdOck>dZT_r zPPTHFKYitY!OzxdnLuMTH18GHIPJ->cesz2e|u)?hdS@#s^r^y{yHtJ^z#^}ng1ao zn0~~`-GXN(oNqjLHF(lM!pyELJPv0<+V>n+fttGGZN(!~p5`5Fj%NN#g1FMB9A=$25D?vJr(dopa!TQlK{6{!6{IOd~jl3Js*IilaF%8!?qvKfchPaIJ+|}6b~>DqJlArxt$n= zy3zUPPr{2FO%}s;AfJlUQ_Aqddwf3CxhYRy0oC&uD|x$S7mjkn|9^A$|DkRAR>KRH zT2JLh{ka(sWDVHN<4Mz(%P{{E*HNOW(Qcp9^>s-AJfh$c`o(+0$Qx;DK=4;}y%i5YC|M!&Tsx(K&`@t`i@p}~bAsw<4qmgTK-duMUPG{w@ zO!Ew_JzV?v%ODa=+KGqA_aE%i!-%z7N;0?&|Bvl0z4pt4#w?~!SOQkLr-f;#FAg57K8FJrMvAD3mZJ? zgriZ(+v=~8-~>M9m5>r)6iz05iPkOiq5CiyjR*bhnr`X#Rma02Mqf%x=5ni^AT1-j z2r59pD{*wqeCHNARp5ENYv|#122Ngwr8M`T?YD8p9|G}a!+XdL1p5V#=dIjK@7#%) zSr63b<%|N6KH_`fWFc!`Nfz7XUHgP;v-u7Z9LLV7zU#>1+4QDcEt;g4T_g^h2(w8H zkw5LpW?`zyk2lOu^G$}Cdb%tnzn8)MP0q&#!5r$b(B2aO(jTph;$}uxTf2U>{w%sb zO~C0&U!pe;*jHD;o9+p@AE&!8AhI_2ZaKwsOx^`ON0np(D7Ly?2&KSGY9D{ucQ^OI zE!`8q{M+g>AGRh{ZZM3~ggIr%Z!iicTTPBN{AHgrB=4!K+kg+-_qkT zqWk>*oALNR#RO{Vg$btG2EucgQKpp(M+bHGPTp@Vy@gzBll9XZJX_jzj!%)3jqF#X zHSW6%55!*VrO)16Tkac>YY~gr1LQCHLrq2w6Zcv=0v<84g3RNZ)Ho%K^@gj)B#4C< z@RGkzCpMtSgL%xA+waD3bz4(EWSH29-IL<2l)I$|4Q*4uKK%$KFgMUDD8pxo^gE2l zYjbzY*5-(>=RZqMuvncevjSGG=f}JRDBQ^`xi>tKEq#<52Su|axnxXZ0ZvUBAQR)a zBNp>7>KmLsSytL5TcYSnuJ`z0QNiPkH|Du-_b!9hb>E-W^X(;Ok0-~*qZwAdByZm# z!?k(4>%MpM-qVuTl91eIXt(}4HHR#ve^L@0=YZ)f${76&D1i`XX)a%eADKjcNM_v$ z_zPH2+-j;UTIO#HE`|nG%$ErC4)!ihpsi+_C^Y?;y>#eu{M%4S<+r;IwL!j-PssC3l!k)`F;-iKgqlWiMbEMGhA9lDsYIQav;iH#RKKasbv76zzkSI00jy}!q z`nR(61q_xJ1wugx_2jSN%LX4)-iFh`!&59fdN0pY4 zgmXJ)#-(Ph-!6Mr-}_@@q|ed+!HgtchHo6@zL!JDD?qKk9KGTfn_S<6s@*2TD&;gWa}_Ob*ZlHPX6;HZyIP>taU z5?q@Ox`(OS(Wf=-gajUqs)lpVlBxU$%(_8E=viq!5IV z_Nz@MLu~K&6ESLDe9a`)>S|+Q{tT+}R$pTcWrWpHs(8gOUTePn z1$323>PiX>UwbgR<;D2LLo;M8=N^2k;d${fNvpIcqpyiICkXG{TP&)A zDI-u)oWFd*zt4B_ogTT>5L*x$X39G`GC=s*r!YI;N0mFq>ZtMCwUt~Ed+S$e8*tFj zL)7THK6Y#-a+u85LOgAC-D=%UTs*L4lrN6Ff5|@+B;mAiJj^94_ic>6(i50u{hWY5 zV*Zcu1a!^v^x8x3U4yh>hfF$x>kp%U?=n08wEd{o`R=b0_Hb-V^@H=D6JoU zc^WUu9yMrJp1EXl;{*-7&WoPF&?~gzHonq9EYo(#`H(fW)FxSWI_uBr1mwnVjoeYr zxjs{WG@2n4DwxDSf=kegOr_GVtxtHD6IhPJ&>k8zyGCi=W%tkWAv5Mnzx3@&x}XNv zS}j3lUmEYQqoZ`l9!AXOZ`b*3$9XrV)F#LnxJK3SU|+VEQ~nQte45DLotN0VnajYN z?6p1quM_Fd-NX^kn%v(Uz`?U-t@#KI*$_nEL@ie@PeAv^?JVzMjc;9yAq)QNJT)_#l63~gVoi~?rjGjs8UwFCMZE+5E;8R8Grjim`{5B~ z`9VzidMYkn$kHtzD~ll!_=z#Mi@NP>=@%txV91rY9L8hfLgIC|P={HZo~HASZie1oBR0wKGYPsHsXv(_$m~K=gj3>@@`wUE%7;`)2=CP* zxE!TXT-PfV_V{PsLdlpd7)y21Dj5xAiNLySD=~Ky``LoBw(Oi1q6AW5e0Z?sBphQ@ zvs|XInzF;%Qcl|t!s30io@vP#6`FJ)+J|MZ+(tCYhgsPnz`FWba)L2^VlRYjg><>q zGR0l1Mf?c?|EHiN$}oEYyEGpHG^kRpmo(GLd}GkHa>NT9Nla;}anGva_>hxHg*kjU z;~C}PMXj^0xX60|-Ib#;+q>j+ClSv&n-FDpgXfTu0aJm zzeDC%&7559a<{a5&>6TShBX1LS1tQi>wo4U|J(8Xp8@8EKayrQcaQ05yqDKD!%AWT zE`|_snR^&V#uqY*R2Fr$t_&K?Y)Y)ob{VMJ(trWUzW~X$XGJ`ln`|OJ1MOc?3Jyvd z8=s(`#8KD|Os?%eCnKHA<|JLqyAtHqgDm+5P)dwxxS`+2ncQxdP$mIBR@3T2pI~7{N9V_`Bpc^xt1_%H*-}v3z^Pc7J0>^CMAb}|JnPz| z-Fehs`@vf4xT3~84NAO-1@F7>&`{U8U3ZWG2Gc@b!n}Hn!kD+e^Xsn+M~0v4EKnl$ zC~Z!Ek}e?VxvGN0YHnrj&Bm&w^-JvbDfaCS2!w!x;STXGIUk@4xUIU7QE{@rsT~40 zV)ZSvO?C2LmoVvULXJ#o{qen)bvf1R7{Goo!bUVO_84DO=Wsa7q)x)W(~k7CSy|n_ zxA9piar$>r+>%}8cN&P@W%@r~DlZs)EZf!fNOvWqB^e+8P#s=*erUG-=$enjbXp_# zz>%;QT)}I&t#rPkDtATF^>&^q<**`zny1oJvth~pv!uSr+L_%Hxy?0~57V$#C6(vG zb2BZ>R%wD48nj8g^`JzSq-#TN0$QgY^45aV!_~{kp*;CSR(FE3R;VG{KyyP5Jz-g% zTm1dFBvVjjw*IH~E2F25(5Kuj`vW=cm9W zAG_BAybMi#S`@y^Fy$`ZV;nmfkm;b5tofk42Pd0JtNTLi=wEZ6J8mt0CN^BcJHZ%Z zW}X~umo|wHj2*nkgIO%St2xeYx46sndh!;`Z&eCcC!1c~QDpYJe^vqI>{>ysy3Jdz zPhn?~w1iz|84j?wS7Aqyd7>8Y``KC=`)OLrMo)2tc$Gh53r@%@JN$TB#hhfj#9z`f z&;D8x)sVyMSgA*V!d3N6_NSIjP1x>gSIv^7&=qBM!dEBWz?X}Iws~nRj@G!{)b(EC zWTMxF&N$qu=Z;=kn~7E3ycIo`4hCZ_p`7dF^RNlXQjI`}Q$KvEs$%B`F z-5~-0awa;zpgK}+N$7{kpwo@`Mvf3E+UH@L`u5L1)%0SM{{V6JfIMyp#h@J!TcNd*-Vc=~-jtx*W%@FT%tf!XaLe z7aus@xm;lRJcfA9ku2Wcmj49|3ECaxV04khP*S!78DhgqBjO1)1PAjpFU-F_dOZW$p$Wc=QK!UwZ(gIw5|e1YKiSEyXZnvA6F_3&}?*gln$ z*}jv(xKC+Ru)IZ|yDQU&w3nT{aFMrKj+ns`-zO%1rfc%?4gc|uc_Ci^J2vo8fAi?_ zb#?9Wn&D?2YL;guz$Qj=EiVU>)_E;D>3r@-?gkaCMtb=I^Z%NWRBo+nv!A^kCal}j z42)T2r`If;VnHJt{!QDlEQvMh4dYS?1sx(r7p#>i*Y6uApPYAk=+_Yto#JXsw=C0i z8Iu*gDNtmvel!p+pWCtLwn1g!!kBA|U~6iG5l-Bb?!=>ILaq!9aPe54KnBO$ER+Tz zozdyh>5KDUH99@lVLkL?Aw)Q%ySO^Iu5XHyxav=y3isd`)AQ;8s6NJE;IZ77m0u{b zXixPfznuexps%Qo6$Y9BF>3mVj8=UWV{6Tsi`)rMd-t+gdy*SsfG)G@>uKZa|Bg!U z_Zs4Z17E_x`?>m%R+26y0gXMMTEQNOw&N`2tIPDqc2h3g z;5JCLhAgMuh;#_S_&wz&O$k5mo3PkMNYu$%Fyx$3drT#Y#{_8aGuqxGx3ak1>Gq+M zpiYz%)&c5q?)PnU;be1LC=OIxkQewU{k!40S>65VGsF52TYoH$<&O&-i}kk*JnwVr zOX5RSNeE)y2ox`1nG%P&6?ebYbmK>fQdH4%S;xELH+(O9qO%k}lB!|BMhWN{?SI$Z zRq^*wnG%5@+rcmphkfA-jzgZg>5%yEbjPmyd)@PAwFX)fPtP_S;Ro{kDhYyHuxO}2 z33U5$e(penZ)vGlyLm{Mq?*m~VgNM|>Rs|}vlngYj%=|htIcLV$5j71%)1uxMNzwS z53}Z``JZt8VnymsJki0owq<>GlHd4-IuAFUy9PmfI7m0gfi?eerNQ4a+>9?S8~d_;Ur8%vhV;!}uVtbQqzviK7JKsy;s z$v+#6J|1w(0A(NgOj30$jyE8(*DuXR{{n_TxwtZfvD7;FuAi?mK=|Hbr$>EEW~7KD zx)YlD-9xm&<+|U9ys1ln{?9({gknrFmEKm9Z*HQF?8CL4VLOYgZ%V>t@<-|Ht~yvU zr+!wUPr`5cl-3P$HJ?+}+aBstJH^Qc`Mw zHgyhD_a|fG7+t!6$q_)wFcn|wv%C$>1IZGXCn~uAGE8jCdsZyo;lBWU`F9~JOP=+` zaFmi$5-oNsY`_VO0xR`JPfXk$3!9tfLhIRc?BP3PxyCU_&nFGJ)nz(z;BgIE$eC98 zT&3HW&vupXymFihO5aVPqK(c*1*hP0Ns)9Oq6G#g>bY-R-19wO%kuq5iOUNDgrPk( zSQs6kokR}R$M=CW%jIZF9nym*;OjU~pAj>cL+#G-MugXN=g!aSb zdh+($!97ICGX`YILWAi0IyJ|Zsb}L9ues{PYT7M%dXtIk7mkTS?OQfaOrhroK{Bi5 zIkuFOciQbT!Cl5f@E`Uyh#(gC{IIzn{JJYxefB@H&d+K^ zsT(tgkON=6dwSQ*Lnv3duXQ~hjjg%we*2rrtQEQLrLYA}A!xCGf59gfYvSOL#7d4e zH$@hlxIICcYe0bF)Ah;wL_7lF?+benMbR=L_U)`o(;*S*jxr-8gp@MiED8_aO4=WZ zL|^Vi&eZgJp|cVdh)u5Do)~i*(@C6Qxo<4VU|Z8d-H_qS_!@tXgact4t4a7v-ZJ`} zQA4+6(z?N;<%*QshO>$1?!&Gn*|Tqmcb4x)m!T4% zj!dH_y}`gkjK-d(Z+O0A8aocc-;S@fMH;h4x;}71o&W#=(X)yN?XLY6!3gK2Ohg1 zKjTF0{Si6lEY1b01o#^huc8$8e!DI%Jf+8eOB+N-0#*1qpGNm&;7LgAe@pqy?}bKa z+8l{BZ>F1Be7R$iaM5o$_~3YoV-Y;!kGi_S{!Btts%>wb#C<@=)&$nm0l(4rO^7k* z;*_==Qo2vd9Dc-K|5EC#CoN$Ba3(KChM{;iR%YD3$j8s;FTLLgf8NpgrsRcBrO!+j za#}%kibC=3CkdB_h*2()EB~;s#V7@c*76x^2V6jL!lvSjQPy2I)pFx!2BcL*?ioE? zQBdfKc>=GF4uQPp$Euxhv7lVkI^RrR3ks-2U4WL!nw7l$$AS&P&s!F5=^Pf>VEn|% z7FvmmwH}I2wz#x>F+#XL(ubmMXq^`8uya@JdwuB%flg!2SS-)kPL{$C#3{XqOdooW z*2);<_FDEsvL`*uT!#~fyiz#V<#F=Q%>CXkyn5?wUHOv&gDp^LKT+aMwssuk9C%Nl zA`b!*;v%n*&)L3|mCI=pYp59XSfQ{{U^8+hBEWbs+DW(_M4yb9wRa_5Bc?xVEs-N|cq zOV{)n>yJNOWZgQK>2F_xsjGtYVqgdcM*+6;zz1brQ*Mj;%hV zpy+_i(wf*?WN&RXyH~*v3>HqADJV%T0xcrLcaLcVt*SFOB@KUpyw$c9sq@3MJt=-Y zxQIb1aTYziLI<$kR+iv{mq?i;v9V!$`rjCx_UdIKfiix zcMJsYb!vair=DAU+TUFl>#-i{5jM*CAv406@rmT#m)!P3ZuP4zOCeQpY2Iq3RKq!{ z=T&wbnfsgT;su2-JgNh?x6z2;-JHNW*JS}+(UDX9tV4=l+Zl3kA@gIErQILel7U4X zl!+$BGL%tbhZ19wqnuK+xlfuF`7q;o(uL40j?g}cN$>D=@65?QI^gsw)oKZQ+2PNS zwI+lLF!sq^=dJIm7k1TLWhHN~!pg!&Eip@@gwzu9|C1qBi!Tdr*qF^w4=J}d=Mc79 zzTekGOVaJ6Hn>5zwEPS8#SKAUHrrp>^Yy5H>73OG!dmF#l?opUe-}RN0oLLXOrU`U zq)Gmv`~sIk&$qB$j!dq*3-O1Z)*r|CR${?0?zL?XwmXqPJ<{f9cqE zxJ##0V?Egt_!eQZ#~i6>sJ(MkfsoF5k+H$EUwFD*!#}DZmOe%tJonm@>*~n&FW`+u zE$s6hk3=r#L|V^enX&f(RO;D3{LyAx%TS?$40?_UjZxW!Z7HaqooyZEFXt)ey0+1X z>jwwIc-`qc##24LBkpB&D?BraBcA%jE4v?fwQ^)aGC0D*E=!wOevQE`8yZJK-HEiO z&eGg2XNc5%Xz-V`z?sp$J+g^R-Q>45LT+$Z1IwdLr?>_DMKYno3FlKe?Yg5A-Tp6* z-5q`LJe6pzs(GJhBtL-kOsa~=PYn%Jx0XCSGxqqgez)T=~yAmbX`STE2 z@FFg3S#Radm2zd{2z#e3Ce7%%q9@~Fg@jyJC)lf~`Oj>7TN};h>x}0V9!UH_Hi1sG ztniOT@*i$**yGK-9L!mgIMFkoCdGhUAkTBgt95AGlP`7%=Y@3xHLXwxb4F$H8}yrL zTbj8IkgzSZ1X(+{GZHiWk}*w%dp12j9>sQU<-h~i)wO{CR6lLw@&tCO?}yqunJP_E z3mMN!*}?_4Wm8NkA=?xd-53@Nqw$c}<4Xu{yXdl(I zA3b2YAv%M1cSa@)IaUA z!`>E0cj5sdnaPRvjbq%sZiHv3K=4M)miQ}Z+(E;-`T9&HdtK(nv=(EN;0^C;?I?eQ zxdh#@T;~`gJdgN^gdJG_R)r)j%7;IwUygjRpfu0hQ7rF2CE{_xVa?u8fG8L~hWGwq5g?-K*;t4=6*i&_U;a(|tQsE=% zRtI>gQ}4+E72Oan8rCjtq*CaGIqHUuKS$2_>{Y zB7AR$`ROMgf{u$>xRVaMaJaCZ`;=DCDo2@8jrHxc54LT90N_mS9X8~`_gRXk+8Hv9iO3ZaEEH_xY39OAUXr;|l6EQPD4~&ma&79%knwxZIdlPW! zcdIGP({e{%dlhBI^Sv4s?xw4n&9c;s>w7MDz+9ZeYA@B$K?qXkct43 z)mgIJvIUPY1yP28MSjMiX4-7WUgnH^hXr0-`PKWH?(eiW)vO>z!gRy?vukX29QQR_>NtB3-BFr|0vX?g1Zh z#=m37to{YKbv=TKj2meUoK4rT-+Qnk>lwU__VRsTMe-?T3$39i6KHLtO-`Dm4q|h{ z85UR~{eH~NKYwvMe1$k7szc0I*Sg-SlEv3d z)}Bb`u0^&st;kGMI{>zHN1AVsT-4oQ`9Gh_?O$o-UieG;jsh%oia?h5^dbPJ6c#jG zqK-@Tqo}sB#h4F`mgo}=RztcExJR>U#%KRXSDkxJZ|sy*wPjpJaqGk|12@; z?N+!}*7xD{J8l|2jhX!+3n|Xd``D6UP>9gfm=mo)QSR*?A`6IRBA+VlMu z;$JOQo>Qq29D;Q1iAQBU$~Y4|7RS$xD^B3myL*|>?{z|_-`r(eIw|2h`;ni)=qTut z8F8^b$z^J4%l*+NyyJr9nq2SjUeTMST?`${L`{k_rq=PlD{YMcS;jeE)327Mc(K(0 z@bI;~b%cBbyajW6pZ7j{A{BY3Q*;=ND|7nF<F<2rutB z;l$j_1w`c2PYxspsZQ9KNS=4OSJVB9t1w`T*fwbhWbBj0r>gf^3!XTMBNNFFE~$M# zR-{ENCM~VndPClW(x#|;!9u|a!>p?II&zok`3U@)_9VYPExrE!(2STJ2k&{gJ;Nx% zGwkidh2+2rtJ9?hzo`>?Pxg4^cA~P5o=>S`)u;lTz`5*C5MoZGJG;F z;e8C@_O-&C0hz?yE7MT08(O;qdd_o{>{De-nlMe{aiG{!xbb#iPS1DIc;V6XFA`U}7NUN~wB5QnQzZUZkUjg7*K_?48W^JlH2 zwKMX$dAZq_g!(e9=?u>svBudYd4AzB8yqj555A0Zag@-0D66{cctXfjk zs#3mSJocrZCDk~Bca8x`^iQ6+f8BV+YiZzjJ*~0d>d2hF9|jj#ANW^S{d2dhKi2H` zSzqU(1$)MW?g1w+;R_RD{D+QPY%(1jIpvl3Ho@34HV!pC#uDXDELS|qi9zPTD*8E# z#${Scu}q1lX_lez8OHRKN6j=M+wCazV~x>>j52>R9%HCPgAqen&{|HQ$ZP(~cQVhI z>Bsbc=nI@ca-K6IIi{4~INUsm|9#>pF4LG??*ke0HNDj~;M3CTdZz(*Js&zxv1PDi zxw>x@%y%wXPS-<(24`&^YxxDSn-x1Th;_Pb3g0kdLJtvtzHWMC z+Lm+PR@~{oH-!D6{^EwYd55Msk?EKZepP+dT&r2j=sEtN$TR2r#Te|M2B-7LWuRIF zCGDJ-!(EIB1OCapqdNZF=U>K=-Z#@hCf5P z4}SrRpDwworF&Sxve{|u4x(EZtMHMPsR+8i06)l>X5pil1gP0ec~pYKmC9>e!NF;a zLppc>YrXBw6EZ!r<(3P(;9DkHU~(SK=Tnb5RVYB!xbKA%kzR@h8CbNLP z!p&nJ8t7o|vn5?liI?P=Rbz}12)$Zd;Fr;;uw3n1$pIxahJWOR2s*z)vpB3=UnD`p z-RyW%ya*YB_MkLEo~BzY))k&oBq#YdD+`&eD<%Dx5B*U^1>oU2da_nQD`+gl>K^c2 zRLI@rElbLI-N@qC8g5((_IGq>3CZe}5Y&}I5nB@1ydu-*S7TFgurv*(ykzDGh~ejk zwTB}YUm4d*{LdQL!Qpis5Iu!GbiV(XTaA2DUAk7|rxeDQfl?!nJNub zKT@?)Jk8JB>sqKYKCFUSw(kz<>Bkflk95G+pq@spfRy6SKsA0QGin2GTof+4gnSf| zn}mLy#H3%h7z|VM;+=1<@iz1Vpj{uwfRflI|JJ6SW|(~r7@?}Zx1aR;6kY<2vc*E( z)^ryp?!?eM5;x}9=K%BOOpTL;srS(JYPvchMt6VMTCe$3zQ4!5r^hcctNAsIS;53W z54iT>YW@+=lUN7#Yd80-PoL2hWzsh+{E+XDraLiw-a*pq{;YJ01X=E+o>@S(o$rZN zg~j48O0t!By6{Te#AAWts+jEg%lmP{VVv1bU)TyA<6@RYZK}-G7DG@R&5>~C4X$tx z=v?m~vhE$Sps7VHHtL2+__|TAYJ>&`!CO8}V|oe_4AC4PsYQ@+-WuM`pvlIUhaw2` zRW%vhZ|dvg_s`nq#NQu;h7#;RSBc&S!irtkk;=ADMR*25`Gi?y6gJZ<2{5Ge0EdLZ zR*zsiAVNNYRM0@~Q#eMFMp3)Kd*!Y?2eFHsgrUQK8*eO30->HS>dr~;+H7A||K78M z4V+6dh7hLf_lEk%^LW=mAS~*i{jh2T?*Nn!q_-w$<4n(Uw)O;#{phEp5y1(w!fQsh z=@(~7&?!Ur$lUAgIqYo_%2F3%rKqVW;ruG#ii}@nWP8O;|603lOX8oYJMBWbkn^99 zA)!}h$g#u=a9iFux)a7{!1Aq_IVVEJ^TS4d`pQb#>;5aMl9#l4Q$oE9McP>n*Q1)h zs?EJM@T#`cqvtEWib|jSGeR<~_lkcPo&`Kcc;y9Xa?(Ba|8~XwR7u1mogMR~HcK~D zs76ewexWVsB>n;=i1y|1O%!?t&Idms&IWyK}{FI(XFyOt;a2_Yoex zKLV?E7g&07{{q1KcNXW8AmV7HZm=yar%)%M@SIN9j2d%%z;TPtJ-n~wiK>JSBhwD5nkTIwV+id9Kl)ma}dY+^uPD z&)dX#XI}~Rvd3YJ({oGm9qTfF#}aM&9iMl$M;pikGNfONel@utu;2gs{3;qMPY7Lk z)sk+Q^`^AzFF?(e?F|!z#t}IeWqN?Uy+BZjh#B@ytm?%Pf0v3_oX4Y=IlKh@KUAmxsyJ_2t z>0tX(elv#{;V((- zu?_|)4+sdWo?l^C{i`){-)-+J7Og0j28 zbp^BeD}k%I8p51WO;Z~02nWUTh0*=OXL<>(6EEt{kSF>X(RY*#N&#B?`Dw!($N1P} zb!M>!?Na`?BgqS*!#SD%v3pGa`rO!790%*;xvZ|#kvn7d(Bt$C8Jt4?ypjWy+A#G% zSiB5jko0@wd6*QCJjFL`>xq*G*PxqX+KLZ>nCaH8e>1FY)D4E*eNgtU-BgC08F&wb zy^HR7R)QtV?XKM1H$Pi-mp9zLS>KE6oZpW9K?$`VwSKlx1`;c=&N7tNcBS#3uJRhYXi!$AQ;NZz{^EBs}y^29aL)9~5OIk~O3;MH$Ut9|H!35-q* zwY6O%d|YqHIN!~)&~1obDv1(H+uH_yq;$~Jbyu#(@dd6InM?J)5v5o<~9%IGO62RgZw`SM)JI3!tCMQH+*Jh&~GE~w`E%8hC#L(z#Lw!j<5rsD2P#QchUa4CB_aFn+9Jm_JXOQ1=@}$Jet%35_0cYp|VF!X0JP!jDb(oe7UCxZW2Lufn0PC;=b2V?`bFWnAuit;Su()CrKoI5d`1KrLhFmtG8KGqbbFv4B76>KqU?x=2k5SX$( ze0r4_%QUTuyT~0p!I5gQy71k-?zv=b?W;(q?0ZBQRI7#>aCNLc>pQATtwcs@opaXz z)f1t_ygABWcqnIyxgm)f2|DW#6)P;>cwWqZQCkG}Y6S7UKx(+8VKQ{Q%y+Ij{vVBf zWmFtpv}Gef0>Ry-kpN9_cS7Sqf?IHJ+zG*g1{!Dx9w4}D;}Qt&?hV16PJrO@4e!l* zKW658v)<3Tw`#4bRj1B9cb~m?8QB(plrC8rcV!k(NY#ziOLiNi2~7;d7$luBHs(9h zaC6T464c?a7d7@dY|w&Y0K-od>6A6s@y0ihEXBOGuK6Co=WKq83HlWt64cFMiz=@b0@^uxk^#JyS=f_HPfvD-i}d zu=2iSFqJCp8||<~KG~P!G(@Wd72F#i2Vb>hgtJ)A%-@CrDq12xv6}@J%>QDSjs(>w25g?OhmJ6{~^9$fm&<8|3tz z6a)Co9E!C3>hNh~np_|@bFSBQdH?9({5Oo3^);OkhL-l3!Nr>D)t$Z`4>A1&aY<% z3XAa9yaF(sFfx`QOop8byg0G@YI~|IqhKeT*O1TKS~02uF0%M_%FMo6LqpxS0p7(u zTdK=~@=klkO4HwRwEKE?nZR<2B4_~N;F- z%+P{g0=H-GKK)DMzQ9^)UtI5iO!;4~KQ@#PN_h=Y%WNyG~n z5|shgiHc@RRh+_IX&UuOFER}UJDiVtMqjM(VMVY^WaVXMkuDD6$$rDa(LT8hgE;0w^tUI#a)n)n@w)}{l zI8mnpYmQ({O@F0TqGVq4tEhLnUhz&=#18#5NjD#}>A71Tu`(vRBdjsl#qkf|YIkMq z>RcVwpC7L7W^P&C{Thpe4jTdX_;jjKX=-}=xbGHLm!z8Hl3l5bCU=qeX&M!m3XJI=B)WVo$(TGk{P(RuxTGv2yJR7p~LfO z_(8z>LhoJ$c4x3FyNAz9Q0^Z^XE^Xq%CxuUJ(835qiWf(gFu~N+3oblf%wmE;qAa$ zU`~j61T!;pW~+@~!WOk}#-?#A{M`Dy7sBSoTuNIH_8)MpP8`VuAq!VxL49c$LhaD` zFIE#9q{9=k02()}zg6wWdyQSWkf|{}4W1_Z+E&cp8PaN>1j9ZmRs_Wg;oH_rR8mY5 z5AFi;%4;cavw(3vB#%DEWuVJw*XlZ#EB6kqL1IouP}s++ z-$Y3nMka0VrS!A>TE11buu*6`R$(wU`h@34sHwDRHC9++`b+?z_ zOs2J}Zo{P=m$5ENpyq4kh?3)eS?=~YuKPFA>SO8!Y3un?m#>|5tQ*eG$C4?p%UN}8 zB*bxLWc_Y>`gFn8%4PWzSmw5tuT=`+U5fOn&aIXg8Arf!xq^v_08y?rG9pO)?-gUT zjrXAJQQG#$!UXyFyeEdFzk;C0V^Fl}v$QHJMvb5zkF;J^`VRrNG;%?&$|c^y+X2Az zT+ey<-_N8^Onr*=rT4*TRXq;ug9?VtUSAdcWZ)Q&l+pSXhI0|Vv~!oa;zYT1R?%){ zo!qVchBv)DL0|`WyMZPcYe0N%;gGIi^Bx?wj%_ru#+a(#ay|yBGbkP5LGsTgLFXJ6 zh^xbzlO0k$#uoN;zU~d*)~*_$e@HR+1#Wjab~eHEuE9zRBLaz)6>akdO%|5^2;T6V z5IjYri68Z?xh9d|S-a?Q$)4A>-`4!m@1Vg{zXkaAW66hlYsmH-7lH zJn_s)7^>8%57XIIrp4nO+)x)QAp7$rTqFj4>^p82c&i|9(nKdkM_4HJx-J@-7wd)T zW=Oz;xrkAOhK5!Idd3G7+#xRzV9s=_O#WeI5}t>6md2va6f@IGLRm`DRjL=1m)&J$ zSeT{bAQEi?3IZ91ON{&`cymL-Xk!@r`~N)S?q%kGq%ifoL^nS1=@4v@oy+1vWh#z= z*z!))I24uXf&%99`j%c)KCJ8VSqC3-k&Bb6q7`N8t@m-4r|&^c z$s$g9V(=NlQEM#oFpZ z4YgDXRMMAdLpWL|-+$dN8RK(Qx>T~Ak%lwr`;D2orar50J)5hAJ)w=ZqH3wf?D0J| zuWGm5wo6Rx&dT|m+$uHUJmUflq6!f*coev`;?aN*$!D;KdZ{RaHX&u4B4v>A;v|E4zIHg5+`mOhdsngF10#EnPK72H6Zi_uyr`$5vw#Lgi zlBF!Bo`0IYXZmhgcMFs9lX*u%?-_opCpa17lJq@uRt**?3?yG`OyA#O=e@8-^*c?ysGwiY9Vh0c)kh0OYAjL|O*IB~su6lR&{*iR8WbVg8?Jd|7@$vI z>;6Z$l=)3$|8H*)J$a%`jAskyhA5-KLsXf_5c5>>N|tmKlKaJ-5|^63aFWSqUgGPQ zJHiq$0#BslXEF5QEjFk|9niVUQYBlZoW<^CxG23JYJhIUpTa7drVDjPNPN}`7@eNy zMKdQ&w)-S)yYBB3!xc433ndo6;tWD8#%-LCoG<5i1&fPuqns?WUWPsC(_2U%$0T3Q9mv8BF_tU0L|qQ2X!aema2K(FodhV>pt0? zl1UAZ_gM?(9v2XBp=oquZG4Pzx4G5I0zR~P)sf4^NHKw+A)al$o6|acx1wGY5x<^2 z&EWBwRT&Up&pq4w#zSPd#KeJ|<jN^~0~cAO+BZ{me3KO__5KpC`MHxDUkYt(7F-|YDZkPsCWN57@1v8lb>4;11Zz8wv3b%HGaZ!? z5VYa8V|C-6kp9d(YmmzDr#%UXnDaCiyY(=tmM8tW!4dv#xjJ(vbjdqTEXY ztz4G{=F$WGRo`NVR2ZnU@e)z2#MsjMi;R4)=VjjM;2D92#@1z3z`xeQ|2nAh+P<)R zSY&7Be-t$T8CZJYiYn!jqc-nspJFY)UDdP|3=|Q{jb_S08npK&PIr)ULR@h1_6OC1k&&`8V=@o9_-$Yq%A@*+9Gm8dB6ntvOTzCy4*>K` zNaKj9!XCK3e4iqfHSS3brTnzYVVJ9~affkJdjiJ`5E9HJ^WZ4Bz?^zM4C-h-MfOHy z01rm^(b*jCF?b9mK#zkjo-ro-}7E{dy1S*L=UJ zW21nzFQiScykE5#mh^~EQzV^VQvbp9z*j+#&oq^J+A^pOz8s~#%?)OW#2OqOxu3W9 zo)nr?&l--Wx{`^YGZ%963{Obk!4sKq96cr@H0kU}bE_=HwgJVNTR+JqIvIHyvvhYx z4f`(Dw1=&yz>YJW@i|6=P${DETf0}30iKr(I#dbKl_&7JK$mx%g6Q-2_Mu3Fa~L1~26akIZD*q+`3QFX+vvORX~Xu6*i%2H+3oyC~+F-29s;&CWk} zrsGb;2uz(M@LyeY56{MiNfWQasmtThgOb!ERV@-H)>})DV?up%{__yOLCORE^-_hc zjBj~sRm&(WbBWD&%x)DwUR}4J(tk7SIFsxnZ~H8_0Qo{;Y^y+I`%d)d&PEKg1au%P zaO)5RTOEnxq&;S6i`llbu^$}aMM%jWzE`dJfRokl1darTF;n{|HPP-^PonQPKb{9U zK6@J>oo=mS{p!%C2hP${kaTV_t#~RIFg`KMwvm7RI@$AEe-rjPNRV6)whu#u8{}h9U(uLoh`pMTSn?#P>x@z3E>G6`*6YoZ%C3 zAnkQq-zRp{w*~bZHczWK233=ACAq$uoC-??C1VeSH-4P*_y=9+kD$eDV26^&F?4)3>FD= z9rK)>m?ta!70P8Vpa-gGzvEnjPChuEgQ-^_F{6L((EayPI;HxGAYe*I}Bgp`0%%0mG!+}Q%mCy z-$>9Q0~Rkoep+^196^9(YnD3Sk^?@={9|jAVNXoQ58>-Ctx6mt_;NDu+~f9|HfGH` z8=7(kF0R4z8~RFfoSb;y-2gp+r>_cd3V4txL|+F+l5@nW==ZhD4LpB{D8Ib(t+*I~ zG%mC_?Y)w$lci5-x6&D2H=ZJURCqtjWi|H~hp^;m&%iuu%~LH?X!F3d z2FYEGFrK9T9nTB^D2k%}_WC=ykZV%_hCq;9HKdd&wV4!zy4yj8AuWUA`Z+&FzR$sx z%$qa!_Okw3a53+cwoZ8qjZOatpv!3K0rniG=ojz3 z6CgampmrKa#pVVkzwWkx2{ls=C^9E#RIQ1Fg~+B-+?v^`-4b@=&y`<-@c}vbA9gjq zsQ4As(Ye`$Z6aALc{}P`L}*)sN9tWFLEf|~PYdf;YnNo_D+Xo~0|ZKy7~+3gT>OV7 zM2d6qdr9<~nlr;zl?58Ov{o42CW>PVg~)cJN)0Z+xdqr@s=dVWRAZJ)EvwL6;A;SuwgW8c3 z^SHu`3UBn@U&U;3MPo|r7YABDk@}4MG@aEZEJS)?Tmv#mEIy_t|0H9J50ksT9dz~{YdE-8#;Agzzt26z`O4%9w;Ry| zkZTS`suA&sogPEkgKdu6x6duwn$MXHW+5Adu<~u6?xh|nLxzN7BeJW^&(6*2R;)L! ztSNHVYC{Rx1fFqm*Mk9&-RkQ4B+`yal4OO~#(`C_vE)wqJK>WiZsM1Pk-b{sy|tXH z>j*P=t-$oCS2cJLSBa%=h_>x4XAfRowG$+ZWR7rQYJ;D@Og<0e0|zgQN3$Iso1Erk zRN-SU_uvZiOt9HUh9ccTA~0h;>u1OcgV!Xs2UKJzQu%`^q}8p7-~7Fsog`& zbm^x?MhgA&l~iWm1V5KOqw4^hV^;)x|q(#%aQeUOSMCw}!ejlXhQ9=G`m zQ;Ma}_8xV-r>%45Zg&tLL`xH-9Am=ymM``&m z>cISkn);6>VL}iN<e^4{|Y2vdcQHs|2gYXyp)84(<(VMQ&3{6{niM(P@tiq?`$6sp4k5$UNChZ{f{G zYY$Dz>6G1SBwBG`otYhiv7gF*9jj?=cDBaJ&dyDTc`gOa-y5Rm%hip9DD_@v+}0ri z19bjs{mTZAbkwidmDyyP`jju>*Y;3+Z0ZoRnvc&IGOvSvKQo7N# zb2~d_Y}rCufH}cYH4=z|{0XcwmxJ(Di0t=mnGP2aZ^+Gmg!Ira=+ zovD1z_t?)ys^Yxq5Z;%^#>r;S6m?evMck_&<}G319ioX|7ojHispJG1M^j6uA&a5ml>ZF|8r^wc5!PUn%>K#AqpKLQlU^p3H zco8^Z1(IKHY~}!ozWve*n?wR}IT+>aN6Df%*3=><7~=Se}Vs zSFWyjZ8kJNxV4t)}(u5B(E}I3agfuu<${-+R+%K0PvLU=6g9w4m)<=?8SN z#KWQ(zAw3rm-kax&!S(2$h3ehv)$=em(z}>mrSa=1^hgd8ZBaFwcsj^dVb2hWt zZ)3NUAEyTc8Va41%ba+W_j>nC3{rJe!(=!f9x?_+*+eEV*vZq<%qZIe4=o*tb_RtL zU!Fv(pjq27oD5$MywXlRb$)R2-C4uMA<#06?Z*OS^Zi|&ilo>{`kV8C*ipG?X$>gm znbot$8C2kG|6P{DuvXYQ>4uKI>r59xyjJtNVEhM{Vyk$q*m+46{}WL~^z@2rt;N_e-g4bDJ9v1KW{2ie$Df|9iYq4t zC^Z`q%yj(&7XiHsIdHctt6i@;XF$Blc-Y$5I*|l)O?tKa4*+*t>h^c$LeanDv;WoH zsXSHHtZQg#CRaLFD;y?J`ci)eukHTp{7FzHn7~jOuLvUp2=a9zZs#0O5t^WIkGmK& z`|B7`Ii*@+yuA7**Q+QM99pRKN9<$EpjqpNUN|Bz_Hz952_#1RtxU$m)Gs!PJ5Q*> z+q>~X{pn>1eN>Oqk^Hr2O$1sZ4}cAGrlNQ2k#)m%-NAM=dQJg{*HV_5f)-0}aHdi^ znhQOYbSH%eUBJEJHPD({W?)MUKcM=FrOpWpxz*I3!vIJt%3q641Is~85SoFvr z4eIcF>l>}wQ*Z`Ff_SE}k7gfH3p(_jZq$auwtkeBx}?`4o$g$v#CUe8j4A;CW85ci zW5q=vZ0|`@g(A-&Vb?$U#T1!&HV36Wv2f#Uq|aTR+)1SJx` zNVQ<>T5l#RdV@&89cbN`rE_YD(la4)U6DXF5O*dQsqh{Sz|bfT)Wb;F;%9{|QkCaV zEE37@%2Jad3U^g5(NS(fb1q$8f%`8NL%D3h#sY~W3%mNGJD(3%bEFCyc_lOo$BD4| z>d_eEn2h-eHhm%8G&I%;-;38Z@ah-uto8ieNn9p9(fr?4dO~?L(24^dUkUYUb@&N* z`f+u4h=)MQf7X0$)uBnO%D&tP9Td1e14HBa#`oSbA}EWRZL~eQLzH@e6+u3kv8Isf zg#~q>(c`ysBGu?sheR5Zp@ClLzGwygN)xe>Bd>;ofX@QV-=m^iu1m%R(|z)GT)^*# zij<-Lx1>ck@6eIZ3vUKM-EY`4&G}a=Z4F3x^qp-&b#D#M29ySzq-D?x=q&S@qvo1AL-ubgc8_b)zg2?QTieMrJV+(Xn&fF1N! zpl_x?UG=mps8>3Lk$uKSCBfC~wp{zhnc~#4`A1u%2n!y@$yd$F?=JXHyr(qC2S;?V=%2KZ)(E0C>0C(}#|NqepstPYh~V;r~|;5kmf z&F21&6P7dw=YS;L-9O`%XC#?{C0x5Zwu!`y-85%VKsPhG?sTzS@-P>KNuV~`fYVLp zz#qxyUm9G;Na%1@)jv}eDUk=fU!wlxBD#@ulUeh8Lq#zjC)Zr7&0#k8Rjo@r*Dsb5 zepY|u6AB~YSDTT0vQ(Q)d-dxNLlQK-T40pZF=e(B7wMw3Rh9++U zeyMldza=Mkb{nl%Zsw-^Lf3H#oM8>$!CJt%hs%0jW$?4A<|vz-ox}WT7Ao?RC@E|V z%r6WQ{j_IZ37>ol>UqzvHrYbdw-4AMkfETD44D51a1v-zjUKA-V>vcT#Qq-b7c(1X z4QpBNW;8>)YD7mDH;gNU5xr>%K2*V9h*GZm#PND?cJ0WQi zrtV7zKKm)pq4a+UVr6T~^v>Di=eeB`RVjnC`v$o^`_^RM81*xp&m&1vfkX8WOROFm z0Du86d;XiWLe#q!eg(Y!?Ct@MtFJ0>e36sM3gixgO=9>qe{l zKNQeNE~+EW(&CoHekqK3fU^m1sno;H{aI|{GT7C!6}ZPfW}gi)*#-vw!JKLMrO+PO z*ZQ>7O2j1Kbe~>J8qIbSyiAhziC#D~)3|=7@ma46VR0Jo%kVSI=A{@YJ5)?ypj1uKHn`NM+{t4R-3~ zJSdZ*43DoZ|NMPTI)^VXvx@l1XybWN9*=l-SlyHK->(wC$H3ZvQu_nn8i%^|4=1G~ z@#Dvn=M{GqADEjwtP!qBef3I(;;h154+MA=sF)^yD>8#J>fi+MNImXz{-n4DUaLVD z)E>~d0#S=OsHZU` zsIsT33ZDGa9LfuWWM_1lq*2SFs?J{s%sw-aDKVLa@sicC=7^udCaR)WczvUoA}Xdaov z>IHo~8+=t?v0jFN%afI>onQ!z5rTymihs_*8_9V6zbu9is!P;`b^WkU%BdSGb9tPv zi91RX2ncVpL2F&k`*1iI%xEFUH?iOR(9nwyNOBXK|>4W?~WUwiPvyNY>S zV3wde`0pm+ZDe`+l)JUBFBGsJ-vT~^K%+^I=Smb>G4b;j8q&6tRIayNL0x1qx1o~B zJ_LI29zvibVrl5PBStnuizq!n} zj}WuM$XW177V`-D31J$lISEc(%Q}2?1~+Pq7`e71e{$fF7mA=CM2-$^SipS8yx0aD5;OE&)Xov5DnQJ#d4G^&7uCK0^ zksSXqf?S{(+=qhe!=bvd|2-hP2478@X((qt>%bT_v9M@-k@06>Jo5_TGh*2NCYSBLN(_}HnpdTXSaBm9*cGFb z{!;OWah`OOQ9J%Cq}xWDI!{4!irC~pj;&t0bG*-kA5-&s?MisEvUqB`g@PzF~16~V=mn+vJ8!dH&D2rua$kD{HiqRMIkq8P;~~ovPenLa8;zjc(uhQJ*~GD_JO9QS*=8A z^5B!q5p%u6c8j1pr6N+G2czz7xdMyt#{qO zZtodBiohmzz9q@SrtY1}LXI7G9Y9}Uo2Q4Q0!kuPF60?6)(Rbzzm$Dvy0T$=QK!P| zOh3?fgo?2%^mrOU-RXCK+-$8DE)ExIb(ZsbEP7nsySu|3_J(?fX=&-nt?B zjDU%q$!;MWz?2^3^-D(x@1*5zy(wOC?<@*mrTz^S8`b@sMqb}6xzrjwAhL7VwmV+* z?1dWmK)C}fxl`5F(5e*URmAnOKT zDH*^26P)Mlq{6KAsk$e7M?v;HPDwts97x24Rq7t8^`MJWyG6yne4* zTk;nI%5 zLEjF=?_H?U@shp)oqr3uq36dk=!p-`OOMrBXG^at1S)-u10q9=gZt__ z%}iO+fYLsA+h_12r$;>CgDbwVp=$+3%)=aWJ;;sapA*uK1?U8U`oX#aazbt>GT0P= z2`uv=X4K(37s)pE_1?wt2rs2^31W%5B0xpOqGcOg&QIo2J#A;RA3+?ib9ZhWy2pW% zAXd6d3Gg>4lkc*c-2F6m;TEts79JtlswuEKHLJQHxL^^H`G*$~W2Fs?Aw!2db7q+-GOsadt8R^sREym#BD$JbEZ`>@pJ zGDPZC@pxKI^}g5x&o?WWTx$D?m{f-D$pvWe@8LaR)t3H2 zxZ6UI1c-o}K?$z0@0|A3O!~!Kex0R@BJ#()onxn$Tt@~AajfL3xcM|p{Dmb3Zs6=w z{e`cKAJyq=re5%LwCGU`hT#jJ86i?{CWU9s}{t!YG)f6vRZd@{n) zkoL2%&iZ0}Uv)9MFp8?nZ)O2fSD5vadZuQru2|8h;D(EFo;|9bDr7!}AqX=^#ol54 z?)8jf%`wiFYX>8LDq6Aaxzzo$3<6qv9ume)9(k-gM2y>IeHP1uxE;h)%&-WbP_p|Z z5#+~uOA)RQ{^dSf*hck}wjVU%j)YX;DJ5~jJGUEXzsGO&58aTkMwy|J@dyyUfbdrj z>~ntYyF&zGq%TlPo~Vt0n231={9nBg?EwbuGWoID62{Y4+C=CY6cW%^+=_cfej08J zIsXtL11M}~dR_t(%3>CP*kxdV)Dr^_xfzh;{5v$aKq=o$0O%I5Ax9wWxB(g3jU^u> zAVG2D6?4fMSA#>{SihyOwW5aleiN62UL(>eUWL*3ZUUR@z2v&^pvsN-nRa9%O$DNu%< z(MflS#Pq*N3)Zk32$2|S=!$PPiWMZxZ&OG2 zcSyT+K~a^04I^TbKM3IQk+o6|DV zYR=Of0s89zKl3T#m@f7}k^VNI^ck6$SKS{f%2s0Mx%ZJvz)DrHL1S0m=sRCQSHWd5 z+rr&gq+f1;+Le+5Y8%Q|;DQ?c0n{#4#5)7UJ}CtD{VxMoQCgQ$`ueRW+x&jK>&cT%$}TUN&Qx7G_tW@jgTF=OPEGFw ziGbEb1PM;Uu@lPeZ$*B@Bt)8}7nHk|S?>>i53~bk0U;zknMH4faDcwhi#4@9sjPl#pSJYKG~F#hNjUdqh_s4xRFeNh=_J)67~s?DnC(92 z3*~}b90YnBTW|U@YRh{@igp-;1;&TKAn60Gqy!}C(1uCQp3><;AL{s_5x6OG{LFLR zO-NzSC=-ga2G{=J02B1OwPQ!L2FJGm|DPR;;a5V4O7L$|b z5}x!(Ma)~`fDVZ(y*-RRjlZui2E@0ZOu#GoM@I;18c<0_ z5-|9-FIGZXq^0ApDMYex_;GIvMaVTPb%W*@0p(PhbeI-^%)W2YE8N|a(3m#x+d^`& zlRfyD#d{J!BM8hNX5h+C2!@kvZXAHTaa5yotc*Fh^AbXB2*~zNprYhwWgKSA&G~pN zRi?OO-XV)IT7j=+qb@#q(B3*R%hJQfR$9q=K~I})7qD5?Taajk@>wy;KFWBNvF^BI zA(=gVhx$Ya5s9vwfNR_nWRPg%>f&0E9Gg_XM_u!irQN5+di-PkzUjshO)A*A05TJG z^pSd`zn=GZ4N6gMhpGU=W$ZHcQzIjziWdj6@HNZlKtw0!S2x;wS+?ZE6*!x}J9daX zV|1bqY8gU2Q~dSHUcynQs?`3?N{N>BPD>HXNqKBTVn#xf&bn0FrsL^VV|muxsvnb; zY-RG(`El*q?k;+JqjTCv?DXR-2LT`?h%heC5 z#Rpy!D!SsM5wMxcE21#$`j^n4w_pMg`SP~JCKzR=&R+6LY>7!*GEvR@F*mZGG%!#z zu&*7Dh9kacBYG?#;BE2M&tH$Zsn;ky>h+R1Wt_v5=#??WeUF=R)Ke1b^fv>NbiB!* z+T(sg^lYxy{jkw84=i%BJEeSTaBQSqwVX!wiIKd6(TO$aul}YuLE750>}2i31bD%c zx<~?0wHA%!Hf?u@=NF|&jdf{MQWeeZ7e!nk#PdZ~P3|qPu!IFl-4& zHq0k?hrd)yBw}&s+2n<0PYfv-4F~ve_lMc9yAW5#0aF%C0O1l2%AbI5JaCk{EdWHo z)El&oU93sH6$dEohp&Y=d`p)ChO}omP*Kor?t>jlb2H!=`B{Yn`3C}p$DaC`Bz_tr!4@gNl3{VIl#5k-R z*f)Ytcuh`nH~=nrP6D~c1`Vj9pX2u-kG&>^EFlsw(L#bmg2uQNe_63!2OowOU=Zay zXHIsl1D9XhyAM!6w%(Z`#S~r6JMRA>{xf-bWs>Vj6f{Hvq(kFkJN%1S#3f3S!c3U6 zllaeG3*Wc5OoYMc0U(~I3F~$I?h>p_?wNhx^b<$-9{YQ=YIb(2aNu+R`R4-wag=Q{D`RkhK z??6jQFqu}#N}pbZm*WmL;61lmVzj&iV<$5o5FlM zT(3W-<9ho^RnA=JLSbkORFvsbgzRAI{>Boy$+XRg<#3E^k=ppD3{F1KG9~=o(vnx} zMoj4ehVt{!P`&^6z(1z`T82nNsfS@c5n{7{9MGP;+DvfwzwLn z&D@sZaJxsL2|;K#;Owm_<>*k)Ip?7zsgk`l#Z5cS#7~6FOePJNEE*^?zl1rGnb-Zf zs4x0#Dls*zJm&oEQA_EcQ6@_Gq<|$d&BIBMDdieT>pK$u~@nP10 zdHb0gg0dx+m#rK$`RF~GUe{d;#C#4$8v(BJj6R>cm>^=>{bBcP;CH5L+Vl#XZ1uAP z5%H*lc57umsw3%@Ls@`ElJ^KZqDy|`9eIK!1+(IP1cTKx@AXWzeR_ANo}FDf)gv3F z?k(4d8AWrw)9oY^f%MxvDt9GBU0d)dj_i>*9S5ok<&@Ras63rzu^H-khT0YASIQYa zZtTwG)}2UeJZiAR@?5DyG@@LMy`U`f?)ueYbnw`rdQt`?mP4`GI66W6@>}%gYRe#((NOBA4Edvdurud8JPOSQW zE;5a>#?ytwE{Id!K70K*pn}C&@*vHWl(yy6FRRA$U{hOxR}lwF4CGQt$VtP!(dny( z%PN$8oZHM35$gu>r%m!PqW7!&@2n!(NjwuE9E*Hku1f8ayq_r;HWwj2hp;}IH7ll(o9nfZ0wynV~`u+DMp`{Ud;O&d`&mx`tdxxvb_W3I^|Tsb=#H*N#O5 z>BY3l<5^Np2MUkB^wDRCaH=D+V)z1fR*EpRD0}XYkJ!-5gp#GHGSmKK6meU_6baMH$Z^G>MlV`Cl(SCq-k z7x+2|>xA>q*_ENkHqY`0hqBgi%Q(CA$?UWuSyG0w*ytv$kGLYgoyxtV+Nit?wSg(>`{f5-l z(C+2Bym~Ga=P`unUg#P6Gu*GRG%J$ypn)>!$h*8CBdsK*9~J%6CEn0foJ_#}V=YHc z<3#QI-7;*y53Bn2)oe?-d2PyZxA}Ec1#h8tglL1|1xKt~9ikkWqP++zqwm@cuBb)z zU=^gLI+j&34fdl9JK-(w`<#Zq*1rVO=*D8b6sm+p)m08YFqGZ(QBg}Y-u1)h(3!N% zP1c;`yP-yPM~BzF$bb3n=GSp<^;^1QioMH3FDk1!tt3YmGu9+fpSrT=jjnLMt>9|E zRomaAaQtKWSoYq;VKDP^^w12s;F3AMY9Cd5{zjOdrgw_qgrvJ3fBVx!p7{ zYG=?e#?OX4*GRc=uuq#M9ywfnMX7eP(_h_4=R*`+zA#0A?_m?YU3-SEO~t7Sy`|qH z-ytT}H+fHQY6}EY*=)>a_5sXxsLss#l*(j){B8EOLSLkENqfJkpoDPhE6V*%@5?f0 zJlgF%gTX#|^$B<(KNXKU-^sFy(qutS1=r)olLN+9>Nn%U$qNT0=4fje>n)SYrl;=6 zUv&E3W@YwU+=@5D=A3f*SDs};&7*>zA|qyk>AAY#SlAlAq=J$gA5vyrwxlCx7sYKl3po?P~97B+PKDS2d`#iam75`}rY- zd>1f{KEBeu2`?3qTTui3$QjnwarvSUYujOuCdV+hnGbW7962}EH}s~ndd3t-K3dLB zCgQ(yD>}O`3o8j_nM~59ESf#l>^inAV&3Y0bZXnBZ}dDg!`=>h;x3rD_t+3#XTtK) z#-4RVOr>!3$UfIa-ZGRBM$fj5&T>5WYVBqoE~IJy;ca@J8Kz;+S-ZQGk!;xe=fIn%}Zo};)GY=cJ@>Ad%w9AfFyYawj=@wcR+T820OSzyM>}) z_B;ccJsS*il3-D_%?rR(b@SRSQ0Nj6QthZXBH&2jr)2>aLIL#(FR52{nISVFHPDRd z0Q%vUneqQx-T}Y^H33+^F%AO_^pXEfQ|KS(xfVem>9t7W><{%czq&aCR*Mlpg6j}t zx&f4V5a<<&d0kW`NR+^icro0sIUa1%K(M%Rtw8-Duv1{QQ%|OU*Vw>a`SmE?J}}At z&O_R3kz#nv^#mlVEV4_h3vYzL2|y*W|6i4qT{0&GGyvEr1Heuh_^w;>vOwWa4ek;W zBn=c6(x%rPu0Tj~h`|4+uTbT^0c)0eOlmg2arzwO}>0&+WOOGENk<_OH z#g{c&v{-X(4X1PO$!q(9{$0BoTFGxHGtVdjqQg(i#KuCkfN2av>CeyB4=|}|yZUhS z`eAeliYm@stK(}pD{LMpGa{-RRjX==O{YaYYS&!7M&LiD8D67awpFj8#u^cXN@Fjq zl+~Y%nM=uua=P&uw$*Z6$SCT+L*2z&?kcTacJeL?6Yj=`VgoM49$pshO+xjOUK%Bd zMbQY-{4L1lF-3F^J-f&9V=1+^t)5u#lejiSyE8E$TDBwmT&A`8yhiWbg~%&8@!^y6 zB!<=uW}oM}!-wWT$x%jT91yb9*T19kODyXcXN1zHz+POEALUy)x}o|<F!(S{cbqPyS%c};|njZBHfuW zlO;?`6_*k=B`xTI%LbC~9))oJP}Fo$S4osXOsczP?LHGV8=a7^i$09L+@H+DYP3ML z37HuWJAnAAn=a2zMwQRsBK#VeB_LzkS^J3b(>*D)gx0pX-K)4vjw zEp>jmb6&ofDA#-}oQ^x)K(v?_R(@={5xq?E5&$eA(wTcCYWXMK!t*Kae_ddIZ4T*6 zEY#Vsn}n)2G@Il)tAs{s7=y4XBjL5%9uQLXFo3g#jguMW5)`+UFoO#RKO82VLqtJ;<7lfe*K~xx%+NV&;P{vJdT3c{>q9=n|y55 zb`sZe`cA687~%1wq)xz4w~4iWG)wB6W_l=cuJ+S!41N+}=90FI-1r51^C6IpYQD@C z2UqmSj-cwNdpAF>r@nFGQVTrzva6lBK?2A%o20%ym*yqI6UNG-*fYXMHC+>G#5;6< z3#oLsXrGVY`$?w(ghDFzt4-f@(#dj(lBYY_D;#^qWU?3w{XWj=AE@~w^cf|(e1IGW%2O2$R z|9|Ocp90#FW0o7&93GwzABtvJIh0v2 zIrptUiSd}#zhrX4eY{gWA$(?c$a*oj!c^!xYuPSqMFt}TGNRl(H&)gQyPO&af43i` z#cNkT$_TUvY~Z~1mv$f9pEN)MF)5~^)D=UvDAXZBgZRjZyUl@#ZnxOY+JZI3Q$ zYRNBdBEzX+dO)HyxXZjUW!0*BPTj3>u$~6_-2xLD?fThtxgR=mOBIb=0zmj-5u%{aEwo~R($=lAGNRNMg$ zngi)qQ*Z~4K(+IK7znyLG&>vLC+o6+fnEs!qNT@Z~2lG7Tw_ z$)k#AJS&@bm+ON92Sx>lYsQ{AyxSw>(cg_q#1Nby3q+)|+iPl(XX2~Gy0Oh+7g?7S zp$+X)rgxg2@Pr*>O5$Iatyz;DU+|!2?V~7Wo_g!Sc_=f^s}j;!)5M)8TfGE(sgDN? z6Y~mLIIkx4GAy=rO3kR!6{F@m@3sB_2!;G~H`zQ{;g)sBbh`iWnWZmVB~J43H>#w7 zC42kVe4St4g<8_5LK$NzzJ;kR>il8YRvjW+yu$V_P)-QBTu;l=R};^_dwqy*z<$DZ zKgOrR@;?~N+CG;n|F#*=YgdrcS& zZi`e^uNG0>J6cHmoc?hb?(LP7n^9mc%c+h{m zDKx}mCJ;ASAv)&qIN&b4Jt1?FsYILo)azKyg!ks^eVZ>u-z?}Q$_5yU2Q1+70(x!> zeZ(ox>TG8);?+t4M~8W;HU0s0rOo$VKYo?1Vvz!=Y9rE0z2ZxQu32p&Q1X9EeG%5OOJkfyu-F}t?9rJ9$G9MWX* z%@2Xw?KkxHig-%2v^M9O8P?x-Zj?`s@N{W9-uW~=imKldg>&}TywPYC7fL*7E)da4 zS3)78*bm*)xVgJ6ur5+1xcHO`vpsWq7TxmvpUN4lt^XGh^rOO6**TRT6n#6$oY!Ez zK2Py9&n9u;$!;g93;B1=lBH1JWGB^sB+&L3`)Q-H@0zAKosdpQ>n}gSXG2A*ZC0An z1QS2X>O0>R#pONeY(}w>bFOI|JrBgwZqHs<{urg>c)?-iTd9+9w7~2qykMsgz4rO# zYaOrI5}ICxGCl@)rAhY0rjueX#YO3}6rYV;m+7yRWhgSw%fSGE1!DfOz(@Zx3+w?| zphl&I9h+`!+1K!$D`qWYW=Zra$O7%n2Y!Eg)t~r92`AOg!$+avo+_t(mKQraaCye6a(?ky>cv^)Cu@ z7=Z@5e3~=(BL%cPPzXfwKL)87h8#vz0&g)B+lic-X9W?_2M?}|@T~ol{X0kxT=fPt zT$jj_;97$?08u2vkg*`of`iUgVsr+7xP-O>a6RsQAq)6HI6#EpWJ=Q@h1#YL`rB5g zauw5WHNJ`mN~Yoicj~QhN#E-W@|!@T-n3n#2vlAK=oLQ{_$ff~I9 z$`BDE`a%##J>LMCgmS~}`p&@$5PBODz6O463ShP84T-Gl3+Xfappid = $wxappid; + $this->mch_id = $mch_id; + $this->notify_url = $notify_url; + $this->key = $key; + } + + /** + * 下单方法 + * @param $params 下单参数 + */ + public function unifiedOrder($params) + { + $this->body = $params['body']; + $this->out_trade_no = $params['out_trade_no']; + $this->total_fee = $params['total_fee']; + $this->trade_type = $params['trade_type']; + $this->nonce_str = $this->genRandomString(); + $this->spbill_create_ip = $_SERVER['REMOTE_ADDR']; + $this->params['appid'] = $this->appid; + $this->params['mch_id'] = $this->mch_id; + $this->params['nonce_str'] = $this->nonce_str; + $this->params['body'] = $this->body; + $this->params['out_trade_no'] = $this->out_trade_no; + $this->params['total_fee'] = $this->total_fee; + $this->params['spbill_create_ip'] = $this->spbill_create_ip; + $this->params['notify_url'] = $this->notify_url; + $this->params['trade_type'] = $this->trade_type; + + +//获取签名数据 + $this->sign = $this->MakeSign($this->params); + $this->params['sign'] = $this->sign; + $xml = $this->data_to_xml($this->params); + $response = $this->postXmlCurl($xml, self::API_URL_PREFIX . self::UNIFIEDORDER_URL); + if (!$response) { + return false; + } + $result = $this->xml_to_data($response); + if (!empty($result['result_code']) && !empty($result['err_code'])) { + $result['err_msg'] = $this->error_code($result['err_code']); + } + return $result; + } + + /** + * 查询订单信息 + * @param $out_trade_no 订单号 + * @return array + */ + public function orderQuery($out_trade_no) + { + $this->params['appid'] = $this->appid; + $this->params['mch_id'] = $this->mch_id; + $this->params['nonce_str'] = $this->genRandomString(); + $this->params['out_trade_no'] = $out_trade_no; +//获取签名数据 + $this->sign = $this->MakeSign($this->params); + $this->params['sign'] = $this->sign; + $xml = $this->data_to_xml($this->params); + $response = $this->postXmlCurl($xml, self::API_URL_PREFIX . self::ORDERQUERY_URL); + if (!$response) { + return false; + } + $result = $this->xml_to_data($response); + if (!empty($result['result_code']) && !empty($result['err_code'])) { + $result['err_msg'] = $this->error_code($result['err_code']); + } + return $result; + } + + /** + * 关闭订单 + * @param $out_trade_no 订单号 + * @return array + */ + public function closeOrder($out_trade_no) + { + $this->params['appid'] = $this->appid; + $this->params['mch_id'] = $this->mch_id; + $this->params['nonce_str'] = $this->genRandomString(); + $this->params['out_trade_no'] = $out_trade_no; +//获取签名数据 + $this->sign = $this->MakeSign($this->params); + $this->params['sign'] = $this->sign; + $xml = $this->data_to_xml($this->params); + $response = $this->postXmlCurl($xml, self::API_URL_PREFIX . self::CLOSEORDER_URL); + if (!$response) { + return false; + } + $result = $this->xml_to_data($response); + return $result; + } + + /** + * + * 获取支付结果通知数据 + * return array + */ + public function getNotifyData() + { +//获取通知的数据 + $xml = $GLOBALS['HTTP_RAW_POST_DATA']; + $data = array(); + if (empty($xml)) { + return false; + } + $data = $this->xml_to_data($xml); + if (!empty($data['return_code'])) { + if ($data['return_code'] == 'FAIL') { + return false; + } + } + return $data; + } + + /** + * 接收通知成功后应答输出XML数据 + * @param string $xml + */ + public function replyNotify() + { + $data['return_code'] = 'SUCCESS'; + $data['return_msg'] = 'OK'; + $xml = $this->data_to_xml($data); + echo $xml; + die(); + } + + /** + * 生成APP端支付参数 + * @param $prepayid 预支付id + */ + public function getAppPayParams($prepayid) + { + $data['appid'] = $this->appid; + $data['partnerid'] = $this->mch_id; + $data['prepayid'] = $prepayid; + $data['package'] = 'Sign=WXPay'; + $data['noncestr'] = $this->genRandomString(); + $data['timestamp'] = time(); + $data['sign'] = $this->MakeSign($data); + return $data; + } + + /** + * 生成签名 + * @return 签名 + */ + public function MakeSign($params) + { +//签名步骤一:按字典序排序数组参数 + ksort($params); + $string = $this->ToUrlParams($params); +//签名步骤二:在string后加入KEY + $string = $string . "&key=" . $this->key; +//签名步骤三:MD5加密 + $string = md5($string); +//签名步骤四:所有字符转为大写 + $result = strtoupper($string); + return $result; + } + + /** + * 将参数拼接为url: key=value&key=value + * @param $params + * @return string + */ + public function ToUrlParams($params) + { + $string = ''; + if (!empty($params)) { + $array = array(); + foreach ($params as $key => $value) { + $array[] = $key . '=' . $value; + } + $string = implode("&", $array); + } + return $string; + } + + /** + * 输出xml字符 + * @param $params 参数名称 + * return string 返回组装的xml + **/ + public function data_to_xml($params) + { + if (!is_array($params) || count($params) <= 0) { + return false; + } + $xml = ""; + foreach ($params as $key => $val) { + if (is_numeric($val)) { + $xml .= "<" . $key . ">" . $val . ""; + } else { + $xml .= "<" . $key . ">"; + } + } + $xml .= ""; + return $xml; + } + + /** + * 将xml转为array + * @param string $xml + * return array + */ + public function xml_to_data($xml) + { + if (!$xml) { + return false; + } +//将XML转为array +//禁止引用外部xml实体 + libxml_disable_entity_loader(true); + $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); + return $data; + } + + /** + * 获取毫秒级别的时间戳 + */ + private static function getMillisecond() + { +//获取毫秒的时间戳 + $time = explode(" ", microtime()); + $time = $time[1] . ($time[0] * 1000); + $time2 = explode(".", $time); + $time = $time2[0]; + return $time; + } + + /** + * 产生一个指定长度的随机字符串,并返回给用户 + * @param type $len 产生字符串的长度 + * @return string 随机字符串 + */ + private function genRandomString($len = 32) + { + $chars = array( + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", + "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", + "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", + "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", + "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2", + "3", "4", "5", "6", "7", "8", "9" + ); + $charsLen = count($chars) - 1; +// 将数组打乱 + shuffle($chars); + $output = ""; + for ($i = 0; $i < $len; $i++) { + $output .= $chars[mt_rand(0, $charsLen)]; + } + return $output; + } + + /** + * 以post方式提交xml到对应的接口url + * + * @param string $xml 需要post的xml数据 + * @param string $url url + * @param bool $useCert 是否需要证书,默认不需要 + * @param int $second url执行超时时间,默认30s + * @throws WxPayException + */ + private function postXmlCurl($xml, $url, $useCert = false, $second = 30) + { + $ch = curl_init(); +//设置超时 + curl_setopt($ch, CURLOPT_TIMEOUT, $second); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); +//设置header + curl_setopt($ch, CURLOPT_HEADER, FALSE); +//要求结果为字符串且输出到屏幕上 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); + if ($useCert == true) { +//设置证书 +//使用证书:cert 与 key 分别属于两个.pem文件 + curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM'); +//curl_setopt($ch,CURLOPT_SSLCERT, WxPayConfig::SSLCERT_PATH); + curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM'); +//curl_setopt($ch,CURLOPT_SSLKEY, WxPayConfig::SSLKEY_PATH); + } +//post提交方式 + curl_setopt($ch, CURLOPT_POST, TRUE); + curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); +//运行curl + $data = curl_exec($ch); +//返回结果 + if ($data) { + curl_close($ch); + return $data; + } else { + $error = curl_errno($ch); + curl_close($ch); + return false; + } + } + + /** + * 错误代码 + * @param $code 服务器输出的错误代码 + * return string + */ + public function error_code($code) + { + $errList = array( + 'NOAUTH' => '商户未开通此接口权限', + 'NOTENOUGH' => '用户帐号余额不足', + 'ORDERNOTEXIST' => '订单号不存在', + 'ORDERPAID' => '商户订单已支付,无需重复操作', + 'ORDERCLOSED' => '当前订单已关闭,无法支付', + 'SYSTEMERROR' => '系统错误!系统超时', + 'APPID_NOT_EXIST' => '参数中缺少APPID', + 'MCHID_NOT_EXIST' => '参数中缺少MCHID', + 'APPID_MCHID_NOT_MATCH' => 'appid和mch_id不匹配', + 'LACK_PARAMS' => '缺少必要的请求参数', + 'OUT_TRADE_NO_USED' => '同一笔交易不能多次提交', + 'SIGNERROR' => '参数签名结果不正确', + 'XML_FORMAT_ERROR' => 'XML格式错误', + 'REQUIRE_POST_METHOD' => '未使用post传递参数 ', + 'POST_DATA_EMPTY' => 'post数据不能为空', + 'NOT_UTF8' => '未使用指定编码格式', + ); + if (array_key_exists($code, $errList)) { + return $errList[$code]; + } + } +} \ No newline at end of file diff --git a/weixinpay/lib/WxMchPay.php b/weixinpay/lib/WxMchPay.php new file mode 100644 index 0000000..a55b33a --- /dev/null +++ b/weixinpay/lib/WxMchPay.php @@ -0,0 +1,93 @@ +values['mch_appid'] = WX_APPID; + $this->values['mchid'] = WX_MCHID; + $this->values['nonce_str'] = self::getNonceStr(); + $this->values['partner_trade_no'] = $payId . date('YmdHis', $_SERVER['REQUEST_TIME']); + $this->values['openid'] = $openid; + $this->values['check_name'] = 'NO_CHECK'; + $this->values['amount'] = $money; + $this->values['desc'] = '提现'; + $this->values['spbill_create_ip'] = gethostbyname($_SERVER['SERVER_NAME']); + $this->SetSign(); +// p($this->values); + $api = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'; + + $xml = $this->ToXml(); + + $response = self::postXmlCurl($xml, $api, true); + + return $this->FromXml($response); + } + + /** + * 以post方式提交xml到对应的接口url + * + * @param string $xml 需要post的xml数据 + * @param string $url url + * @param bool $useCert 是否需要证书,默认不需要 + * @param int $second url执行超时时间,默认30s + * @throws WxPayException + */ + private static function postXmlCurl($xml, $url, $useCert = false, $second = 30) { + $ch = curl_init(); + //设置超时 + curl_setopt($ch, CURLOPT_TIMEOUT, $second); + + //如果有配置代理这里就设置代理 + if (WX_CURL_PROXY_HOST != "0.0.0.0" && WX_CURL_PROXY_PORT != 0) { + curl_setopt($ch, CURLOPT_PROXY, WX_CURL_PROXY_HOST); + curl_setopt($ch, CURLOPT_PROXYPORT, WX_CURL_PROXY_PORT); + } + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验 + //设置header + curl_setopt($ch, CURLOPT_HEADER, FALSE); + //要求结果为字符串且输出到屏幕上 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); + //echo WX_SSLCERT_PATH; + //echo WX_SSLKEY_PATH; + // die; + if ($useCert == true) { + //设置证书 + //使用证书:cert 与 key 分别属于两个.pem文件 + curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM'); + curl_setopt($ch, CURLOPT_SSLCERT,WX_SSLCERT_PATH); + curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM'); + curl_setopt($ch, CURLOPT_SSLKEY, WX_SSLKEY_PATH); + } + + //post提交方式 + curl_setopt($ch, CURLOPT_POST, TRUE); + curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); + //运行curl + $data = curl_exec($ch); + //返回结果 + if ($data) { + curl_close($ch); + return $data; + } else { + $error = curl_errno($ch); + curl_close($ch); + throw new WxPayException("curl出错,错误码:$error"); + } + } + + public static function getNonceStr($length = 32) { + $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; + $str = ""; + for ($i = 0; $i < $length; $i++) { + $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); + } + return $str; + } + +} diff --git a/weixinpay/lib/WxOrderNotify.php b/weixinpay/lib/WxOrderNotify.php new file mode 100644 index 0000000..0c18f49 --- /dev/null +++ b/weixinpay/lib/WxOrderNotify.php @@ -0,0 +1,97 @@ +app = $app; + } + //查询订单 + public function Queryorder($transaction_id){ + + @file_put_contents('./weixinQuery.txt','in_query',FILE_APPEND); + + $input = new WxPayOrderQuery(); + + $input->SetTransaction_id($transaction_id); + + $result = WxPayApi::orderQuery($input); + + if(array_key_exists("return_code", $result) && array_key_exists("result_code", $result) && $result["return_code"] == "SUCCESS" && $result["result_code"] == "SUCCESS") { + + $arr = json_decode($result['attach'] , true); + + switch ($arr['type']){ + + case 'Balance': + //余额卡 + $order_model = new \app\farm\model\BalanceOrder(); + + break; + + case 'Claim': + //认养 + $order_model = new \app\farm\model\ClaimOrder(); + + break; + case 'Land': + //土地 + $order_model = new \app\farm\model\LandOrder(); + + break; + + case 'ClaimSend': + //配送订单 + $order_model = new \app\farm\model\SendOrder(); + + break; + + case 'Breed': + //养殖订单 + $order_model = new \app\farm\model\BreedOrder(); + + break; + case 'SchoolShop': + //商城订单 + $order_model = new \app\farm\model\ShopOrder(); + + break; + } + + $order_model->orderResult($arr['out_trade_no'],$transaction_id); + + } + return false; + } + + //重写回调处理函数 + public function NotifyProcess($data, &$msg) + { + $notfiyOutput = array(); + + if(!array_key_exists("transaction_id", $data)){ + file_put_contents('./weixinQuery.txt','输入参数不正确',FILE_APPEND); + $msg = "输入参数不正确"; + return false; + } + file_put_contents('./weixinQuery.txt','abc',FILE_APPEND); + //查询订单,判断订单真实性 + if(!$this->Queryorder($data["transaction_id"],$data[''])){ + $msg = "订单查询失败"; + return false; + } + return true; + } +} + + diff --git a/weixinpay/lib/WxPay.Api.php b/weixinpay/lib/WxPay.Api.php new file mode 100644 index 0000000..2688c5c --- /dev/null +++ b/weixinpay/lib/WxPay.Api.php @@ -0,0 +1,591 @@ +IsOut_trade_noSet()) { + throw new WxPayException("缺少统一支付接口必填参数out_trade_no!"); + }else if(!$inputObj->IsBodySet()){ + throw new WxPayException("缺少统一支付接口必填参数body!"); + }else if(!$inputObj->IsTotal_feeSet()) { + throw new WxPayException("缺少统一支付接口必填参数total_fee!"); + }else if(!$inputObj->IsTrade_typeSet()) { + throw new WxPayException("缺少统一支付接口必填参数trade_type!"); + } + + //关联参数 + if($inputObj->GetTrade_type() == "JSAPI" && !$inputObj->IsOpenidSet()){ + throw new WxPayException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"); + } + if($inputObj->GetTrade_type() == "NATIVE" && !$inputObj->IsProduct_idSet()){ + throw new WxPayException("统一支付接口中,缺少必填参数product_id!trade_type为JSAPI时,product_id为必填参数!"); + } + + //异步通知url未设置,则使用配置文件中的url + if(!$inputObj->IsNotify_urlSet()){ + $inputObj->SetNotify_url(WX_NOTIFY_URL);//异步通知url + } + + $inputObj->SetAppid(WX_APPID);//公众账号ID + $inputObj->SetMch_id(WX_MCHID);//商户号 + $inputObj->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//终端ip + //$inputObj->SetSpbill_create_ip("1.1.1.1"); + $inputObj->SetNonce_str(self::getNonceStr());//随机字符串 + + //签名 + $inputObj->SetSign(); + $xml = $inputObj->ToXml(); + + $startTimeStamp = self::getMillisecond();//请求开始时间 + $response = self::postXmlCurl($xml, $url, false, $timeOut); + + $result = WxPayResults::Init($response); + self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间 + + return $result; + } + + /** + * + * 查询订单,WxPayOrderQuery中out_trade_no、transaction_id至少填一个 + * appid、mchid、spbill_create_ip、nonce_str不需要填入 + * @param WxPayOrderQuery $inputObj + * @param int $timeOut + * @throws WxPayException + * @return 成功时返回,其他抛异常 + */ + public static function orderQuery($inputObj, $timeOut = 6) + { + $url = "https://api.mch.weixin.qq.com/pay/orderquery"; + //检测必填参数 + if(!$inputObj->IsOut_trade_noSet() && !$inputObj->IsTransaction_idSet()) { + throw new WxPayException("订单查询接口中,out_trade_no、transaction_id至少填一个!"); + } + $inputObj->SetAppid(WX_APPID);//公众账号ID + $inputObj->SetMch_id(WX_MCHID);//商户号 + $inputObj->SetNonce_str(self::getNonceStr());//随机字符串 + + $inputObj->SetSign();//签名 + $xml = $inputObj->ToXml(); + + $startTimeStamp = self::getMillisecond();//请求开始时间 + $response = self::postXmlCurl($xml, $url, false, $timeOut); + $result = WxPayResults::Init($response); + self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间 + + return $result; + } + + /** + * + * 关闭订单,WxPayCloseOrder中out_trade_no必填 + * appid、mchid、spbill_create_ip、nonce_str不需要填入 + * @param WxPayCloseOrder $inputObj + * @param int $timeOut + * @throws WxPayException + * @return 成功时返回,其他抛异常 + */ + public static function closeOrder($inputObj, $timeOut = 6) + { + $url = "https://api.mch.weixin.qq.com/pay/closeorder"; + //检测必填参数 + if(!$inputObj->IsOut_trade_noSet()) { + throw new WxPayException("订单查询接口中,out_trade_no必填!"); + } + $inputObj->SetAppid(WX_APPID);//公众账号ID + $inputObj->SetMch_id(WX_MCHID);//商户号 + $inputObj->SetNonce_str(self::getNonceStr());//随机字符串 + + $inputObj->SetSign();//签名 + $xml = $inputObj->ToXml(); + + $startTimeStamp = self::getMillisecond();//请求开始时间 + $response = self::postXmlCurl($xml, $url, false, $timeOut); + $result = WxPayResults::Init($response); + self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间 + + return $result; + } + + /** + * + * 申请退款,WxPayRefund中out_trade_no、transaction_id至少填一个且 + * out_refund_no、total_fee、refund_fee、op_user_id为必填参数 + * appid、mchid、spbill_create_ip、nonce_str不需要填入 + * @param WxPayRefund $inputObj + * @param int $timeOut + * @throws WxPayException + * @return 成功时返回,其他抛异常 + */ + public static function refund($inputObj, $timeOut = 6) + { + $url = "https://api.mch.weixin.qq.com/secapi/pay/refund"; + //检测必填参数 + if(!$inputObj->IsOut_trade_noSet() && !$inputObj->IsTransaction_idSet()) { + throw new WxPayException("退款申请接口中,out_trade_no、transaction_id至少填一个!"); + }else if(!$inputObj->IsOut_refund_noSet()){ + throw new WxPayException("退款申请接口中,缺少必填参数out_refund_no!"); + }else if(!$inputObj->IsTotal_feeSet()){ + throw new WxPayException("退款申请接口中,缺少必填参数total_fee!"); + }else if(!$inputObj->IsRefund_feeSet()){ + throw new WxPayException("退款申请接口中,缺少必填参数refund_fee!"); + }else if(!$inputObj->IsOp_user_idSet()){ + throw new WxPayException("退款申请接口中,缺少必填参数op_user_id!"); + } + $inputObj->SetAppid(WX_APPID);//公众账号ID + $inputObj->SetMch_id(WX_MCHID);//商户号 + $inputObj->SetNonce_str(self::getNonceStr());//随机字符串 + + $inputObj->SetSign();//签名 + $xml = $inputObj->ToXml(); + $startTimeStamp = self::getMillisecond();//请求开始时间 + + $response = self::postXmlCurl($xml, $url, true, $timeOut); + + $result = WxPayResults::Init($response); + + + self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间 + + return $result; + } + + /** + * + * 查询退款 + * 提交退款申请后,通过调用该接口查询退款状态。退款有一定延时, + * 用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。 + * WxPayRefundQuery中out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个 + * appid、mchid、spbill_create_ip、nonce_str不需要填入 + * @param WxPayRefundQuery $inputObj + * @param int $timeOut + * @throws WxPayException + * @return 成功时返回,其他抛异常 + */ + public static function refundQuery($inputObj, $timeOut = 6) + { + $url = "https://api.mch.weixin.qq.com/pay/refundquery"; + //检测必填参数 + if(!$inputObj->IsOut_refund_noSet() && + !$inputObj->IsOut_trade_noSet() && + !$inputObj->IsTransaction_idSet() && + !$inputObj->IsRefund_idSet()) { + throw new WxPayException("退款查询接口中,out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个!"); + } + $inputObj->SetAppid(WX_APPID);//公众账号ID + $inputObj->SetMch_id(WX_MCHID);//商户号 + $inputObj->SetNonce_str(self::getNonceStr());//随机字符串 + + $inputObj->SetSign();//签名 + $xml = $inputObj->ToXml(); + + $startTimeStamp = self::getMillisecond();//请求开始时间 + $response = self::postXmlCurl($xml, $url, false, $timeOut); + $result = WxPayResults::Init($response); + self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间 + + return $result; + } + + /** + * 下载对账单,WxPayDownloadBill中bill_date为必填参数 + * appid、mchid、spbill_create_ip、nonce_str不需要填入 + * @param WxPayDownloadBill $inputObj + * @param int $timeOut + * @throws WxPayException + * @return 成功时返回,其他抛异常 + */ + public static function downloadBill($inputObj, $timeOut = 6) + { + $url = "https://api.mch.weixin.qq.com/pay/downloadbill"; + //检测必填参数 + if(!$inputObj->IsBill_dateSet()) { + throw new WxPayException("对账单接口中,缺少必填参数bill_date!"); + } + $inputObj->SetAppid(WX_APPID);//公众账号ID + $inputObj->SetMch_id(WX_MCHID);//商户号 + $inputObj->SetNonce_str(self::getNonceStr());//随机字符串 + + $inputObj->SetSign();//签名 + $xml = $inputObj->ToXml(); + + $response = self::postXmlCurl($xml, $url, false, $timeOut); + if(substr($response, 0 , 5) == ""){ + return ""; + } + return $response; + } + + /** + * 提交被扫支付API + * 收银员使用扫码设备读取微信用户刷卡授权码以后,二维码或条码信息传送至商户收银台, + * 由商户收银台或者商户后台调用该接口发起支付。 + * WxPayWxPayMicroPay中body、out_trade_no、total_fee、auth_code参数必填 + * appid、mchid、spbill_create_ip、nonce_str不需要填入 + * @param WxPayWxPayMicroPay $inputObj + * @param int $timeOut + */ + public static function micropay($inputObj, $timeOut = 10) + { + $url = "https://api.mch.weixin.qq.com/pay/micropay"; + //检测必填参数 + if(!$inputObj->IsBodySet()) { + throw new WxPayException("提交被扫支付API接口中,缺少必填参数body!"); + } else if(!$inputObj->IsOut_trade_noSet()) { + throw new WxPayException("提交被扫支付API接口中,缺少必填参数out_trade_no!"); + } else if(!$inputObj->IsTotal_feeSet()) { + throw new WxPayException("提交被扫支付API接口中,缺少必填参数total_fee!"); + } else if(!$inputObj->IsAuth_codeSet()) { + throw new WxPayException("提交被扫支付API接口中,缺少必填参数auth_code!"); + } + + $inputObj->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//终端ip + $inputObj->SetAppid(WX_APPID);//公众账号ID + $inputObj->SetMch_id(WX_MCHID);//商户号 + $inputObj->SetNonce_str(self::getNonceStr());//随机字符串 + + $inputObj->SetSign();//签名 + $xml = $inputObj->ToXml(); + + $startTimeStamp = self::getMillisecond();//请求开始时间 + $response = self::postXmlCurl($xml, $url, false, $timeOut); + $result = WxPayResults::Init($response); + self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间 + + return $result; + } + + /** + * + * 撤销订单API接口,WxPayReverse中参数out_trade_no和transaction_id必须填写一个 + * appid、mchid、spbill_create_ip、nonce_str不需要填入 + * @param WxPayReverse $inputObj + * @param int $timeOut + * @throws WxPayException + */ + public static function reverse($inputObj, $timeOut = 6) + { + $url = "https://api.mch.weixin.qq.com/secapi/pay/reverse"; + //检测必填参数 + if(!$inputObj->IsOut_trade_noSet() && !$inputObj->IsTransaction_idSet()) { + throw new WxPayException("撤销订单API接口中,参数out_trade_no和transaction_id必须填写一个!"); + } + + $inputObj->SetAppid(WX_APPID);//公众账号ID + $inputObj->SetMch_id(WX_MCHID);//商户号 + $inputObj->SetNonce_str(self::getNonceStr());//随机字符串 + + $inputObj->SetSign();//签名 + $xml = $inputObj->ToXml(); + + $startTimeStamp = self::getMillisecond();//请求开始时间 + $response = self::postXmlCurl($xml, $url, true, $timeOut); + $result = WxPayResults::Init($response); + self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间 + + return $result; + } + + /** + * + * 测速上报,该方法内部封装在report中,使用时请注意异常流程 + * WxPayReport中interface_url、return_code、result_code、user_ip、execute_time_必填 + * appid、mchid、spbill_create_ip、nonce_str不需要填入 + * @param WxPayReport $inputObj + * @param int $timeOut + * @throws WxPayException + * @return 成功时返回,其他抛异常 + */ + public static function report($inputObj, $timeOut = 1) + { + $url = "https://api.mch.weixin.qq.com/payitil/report"; + //检测必填参数 + if(!$inputObj->IsInterface_urlSet()) { + throw new WxPayException("接口URL,缺少必填参数interface_url!"); + } if(!$inputObj->IsReturn_codeSet()) { + throw new WxPayException("返回状态码,缺少必填参数return_code!"); + } if(!$inputObj->IsResult_codeSet()) { + throw new WxPayException("业务结果,缺少必填参数result_code!"); + } if(!$inputObj->IsUser_ipSet()) { + throw new WxPayException("访问接口IP,缺少必填参数user_ip!"); + } if(!$inputObj->IsExecute_time_Set()) { + throw new WxPayException("接口耗时,缺少必填参数execute_time_!"); + } + $inputObj->SetAppid(WX_APPID);//公众账号ID + $inputObj->SetMch_id(WX_MCHID);//商户号 + $inputObj->SetUser_ip($_SERVER['REMOTE_ADDR']);//终端ip + $inputObj->SetTime(date("YmdHis"));//商户上报时间 + $inputObj->SetNonce_str(self::getNonceStr());//随机字符串 + + $inputObj->SetSign();//签名 + $xml = $inputObj->ToXml(); + + $startTimeStamp = self::getMillisecond();//请求开始时间 + $response = self::postXmlCurl($xml, $url, false, $timeOut); + return $response; + } + + /** + * + * 生成二维码规则,模式一生成支付二维码 + * appid、mchid、spbill_create_ip、nonce_str不需要填入 + * @param WxPayBizPayUrl $inputObj + * @param int $timeOut + * @throws WxPayException + * @return 成功时返回,其他抛异常 + */ + public static function bizpayurl($inputObj, $timeOut = 6) + { + if(!$inputObj->IsProduct_idSet()){ + throw new WxPayException("生成二维码,缺少必填参数product_id!"); + } + + $inputObj->SetAppid(WX_APPID);//公众账号ID + $inputObj->SetMch_id(WX_MCHID);//商户号 + $inputObj->SetTime_stamp(time());//时间戳 + $inputObj->SetNonce_str(self::getNonceStr());//随机字符串 + + $inputObj->SetSign();//签名 + + return $inputObj->GetValues(); + } + + /** + * + * 转换短链接 + * 该接口主要用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX), + * 减小二维码数据量,提升扫描速度和精确度。 + * appid、mchid、spbill_create_ip、nonce_str不需要填入 + * @param WxPayShortUrl $inputObj + * @param int $timeOut + * @throws WxPayException + * @return 成功时返回,其他抛异常 + */ + public static function shorturl($inputObj, $timeOut = 6) + { + $url = "https://api.mch.weixin.qq.com/tools/shorturl"; + //检测必填参数 + if(!$inputObj->IsLong_urlSet()) { + throw new WxPayException("需要转换的URL,签名用原串,传输需URL encode!"); + } + $inputObj->SetAppid(WX_APPID);//公众账号ID + $inputObj->SetMch_id(WX_MCHID);//商户号 + $inputObj->SetNonce_str(self::getNonceStr());//随机字符串 + + $inputObj->SetSign();//签名 + $xml = $inputObj->ToXml(); + + $startTimeStamp = self::getMillisecond();//请求开始时间 + $response = self::postXmlCurl($xml, $url, false, $timeOut); + $result = WxPayResults::Init($response); + self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间 + + return $result; + } + + /** + * + * 支付结果通用通知 + * @param function $callback + * 直接回调函数使用方法: notify(you_function); + * 回调类成员函数方法:notify(array($this, you_function)); + * $callback 原型为:function function_name($data){} + */ + public static function notify($callback, &$msg) + { + //获取通知的数据 + $xml = $GLOBALS['HTTP_RAW_POST_DATA']; + file_put_contents('./weixinQuery.txt','$xml---ori:---'.$xml,FILE_APPEND); + if(empty($xml)){ + $xml = file_get_contents('php://input'); // 解决数据 + } + file_put_contents('./weixinQuery.txt','$xml---now:---'.$xml,FILE_APPEND); + //如果返回成功则验证签名 + try { + $result = WxPayResults::Init($xml); + //file_put_contents('./weixinQuery.txt','notify:---'.json_encode($result),FILE_APPEND); + } catch (WxPayException $e){ + $msg = $e->errorMessage(); + file_put_contents('./weixinQuery.txt','WxPayException:---'.$xml,$msg); + return false; + } + return call_user_func($callback, $result); + } + + /** + * + * 产生随机字符串,不长于32位 + * @param int $length + * @return 产生的随机字符串 + */ + public static function getNonceStr($length = 32) + { + $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; + $str =""; + for ( $i = 0; $i < $length; $i++ ) { + $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1); + } + return $str; + } + + /** + * 直接输出xml + * @param string $xml + */ + public static function replyNotify($xml) + { + echo $xml; + } + + /** + * + * 上报数据, 上报的时候将屏蔽所有异常流程 + * @param string $usrl + * @param int $startTimeStamp + * @param array $data + */ + private static function reportCostTime($url, $startTimeStamp, $data) + { + //如果不需要上报数据 + if(WX_REPORT_LEVENL == 0){ + return; + } + //如果仅失败上报 + if(WX_REPORT_LEVENL == 1 && + array_key_exists("return_code", $data) && + $data["return_code"] == "SUCCESS" && + array_key_exists("result_code", $data) && + $data["result_code"] == "SUCCESS") + { + return; + } + + //上报逻辑 + $endTimeStamp = self::getMillisecond(); + $objInput = new WxPayReport(); + $objInput->SetInterface_url($url); + $objInput->SetExecute_time_($endTimeStamp - $startTimeStamp); + //返回状态码 + if(array_key_exists("return_code", $data)){ + $objInput->SetReturn_code($data["return_code"]); + } + //返回信息 + if(array_key_exists("return_msg", $data)){ + $objInput->SetReturn_msg($data["return_msg"]); + } + //业务结果 + if(array_key_exists("result_code", $data)){ + $objInput->SetResult_code($data["result_code"]); + } + //错误代码 + if(array_key_exists("err_code", $data)){ + $objInput->SetErr_code($data["err_code"]); + } + //错误代码描述 + if(array_key_exists("err_code_des", $data)){ + $objInput->SetErr_code_des($data["err_code_des"]); + } + //商户订单号 + if(array_key_exists("out_trade_no", $data)){ + $objInput->SetOut_trade_no($data["out_trade_no"]); + } + //设备号 + if(array_key_exists("device_info", $data)){ + $objInput->SetDevice_info($data["device_info"]); + } + + try{ + self::report($objInput); + } catch (WxPayException $e){ + //不做任何处理 + } + } + + /** + * 以post方式提交xml到对应的接口url + * + * @param string $xml 需要post的xml数据 + * @param string $url url + * @param bool $useCert 是否需要证书,默认不需要 + * @param int $second url执行超时时间,默认30s + * @throws WxPayException + */ + private static function postXmlCurl($xml, $url, $useCert = false, $second = 30) + { + $ch = curl_init(); + //设置超时 + curl_setopt($ch, CURLOPT_TIMEOUT, $second); + + //如果有配置代理这里就设置代理 + if(WX_CURL_PROXY_HOST != "0.0.0.0" + && WX_CURL_PROXY_PORT != 0){ + curl_setopt($ch,CURLOPT_PROXY, WX_CURL_PROXY_HOST); + curl_setopt($ch,CURLOPT_PROXYPORT, WX_CURL_PROXY_PORT); + } + curl_setopt($ch,CURLOPT_URL, $url); + curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE); + curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);//严格校验 + //设置header + curl_setopt($ch, CURLOPT_HEADER, FALSE); + //要求结果为字符串且输出到屏幕上 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); + + if($useCert == true){ + //设置证书 + //使用证书:cert 与 key 分别属于两个.pem文件 + curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM'); + curl_setopt($ch,CURLOPT_SSLCERT, WX_SSLCERT_PATH); + curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM'); + curl_setopt($ch,CURLOPT_SSLKEY, WX_SSLKEY_PATH); + } + //post提交方式 + curl_setopt($ch, CURLOPT_POST, TRUE); + curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); + //运行curl + $data = curl_exec($ch); + + //返回结果 + if($data){ + curl_close($ch); + return $data; + } else { + $error = curl_errno($ch); + curl_close($ch); + throw new WxPayException("curl出错,错误码:$error"); + } + } + + /** + * 获取毫秒级别的时间戳 + */ + private static function getMillisecond() + { + //获取毫秒的时间戳 + $time = explode ( " ", microtime () ); + $time = $time[1] . ($time[0] * 1000); + $time2 = explode( ".", $time ); + $time = $time2[0]; + return $time; + } +} + diff --git a/weixinpay/lib/WxPay.Config.php b/weixinpay/lib/WxPay.Config.php new file mode 100644 index 0000000..eea1074 --- /dev/null +++ b/weixinpay/lib/WxPay.Config.php @@ -0,0 +1,59 @@ +MakeSign(); + $this->values['sign'] = $sign; + return $sign; + } + + /** + * 获取签名,详见签名生成算法的值 + * @return 值 + **/ + public function GetSign() + { + return $this->values['sign']; + } + + /** + * 判断签名,详见签名生成算法是否存在 + * @return true 或 false + **/ + public function IsSignSet() + { + return array_key_exists('sign', $this->values); + } + + /** + * 输出xml字符 + * @throws WxPayException + **/ + public function ToXml() + { + if(!is_array($this->values) + || count($this->values) <= 0) + { + throw new WxPayException("数组数据异常!"); + } + + $xml = ""; + foreach ($this->values as $key=>$val) + { + if (is_numeric($val)){ + $xml.="<".$key.">".$val.""; + }else{ + $xml.="<".$key.">"; + } + } + $xml.=""; + return $xml; + } + + /** + * 将xml转为array + * @param string $xml + * @throws WxPayException + */ + public function FromXml($xml) + { + if(!$xml){ + throw new WxPayException("xml数据异常!"); + } + //将XML转为array + //禁止引用外部xml实体 + libxml_disable_entity_loader(true); + $this->values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); + return $this->values; + } + + /** + * 格式化参数格式化成url参数 + */ + public function ToUrlParams() + { + $buff = ""; + foreach ($this->values as $k => $v) + { + if($k != "sign" && $v != "" && !is_array($v)){ + $buff .= $k . "=" . $v . "&"; + } + } + + $buff = trim($buff, "&"); + return $buff; + } + + /** + * 生成签名 + * @return 签名,本函数不覆盖sign成员变量,如要设置签名需要调用SetSign方法赋值 + */ + public function MakeSign() + { + //签名步骤一:按字典序排序参数 + ksort($this->values); + $string = $this->ToUrlParams(); + //签名步骤二:在string后加入KEY + $string = $string . "&key=".WX_KEY; + //签名步骤三:MD5加密 + $string = md5($string); + //签名步骤四:所有字符转为大写 + $result = strtoupper($string); + return $result; + } + + /** + * 获取设置的值 + */ + public function GetValues() + { + return $this->values; + } +} + +/** + * + * 接口调用结果类 + * @author widyhu + * + */ +class WxPayResults extends WxPayDataBase +{ + /** + * + * 检测签名 + */ + public function CheckSign() + { + //fix异常 + if(!$this->IsSignSet()){ + throw new WxPayException("签名错误!"); + } + + $sign = $this->MakeSign(); + if($this->GetSign() == $sign){ + return true; + } + throw new WxPayException("签名错误!"); + } + + /** + * + * 使用数组初始化 + * @param array $array + */ + public function FromArray($array) + { + $this->values = $array; + } + + /** + * + * 使用数组初始化对象 + * @param array $array + * @param 是否检测签名 $noCheckSign + */ + public static function InitFromArray($array, $noCheckSign = false) + { + $obj = new self(); + $obj->FromArray($array); + if($noCheckSign == false){ + $obj->CheckSign(); + } + return $obj; + } + + /** + * + * 设置参数 + * @param string $key + * @param string $value + */ + public function SetData($key, $value) + { + $this->values[$key] = $value; + } + + /** + * 将xml转为array + * @param string $xml + * @throws WxPayException + */ + public static function Init($xml) + { + $obj = new self(); + $obj->FromXml($xml); + //fix bug 2015-06-29 + if($obj->values['return_code'] != 'SUCCESS'){ + return $obj->GetValues(); + } + $obj->CheckSign(); + return $obj->GetValues(); + } +} + +/** + * + * 回调基础类 + * @author widyhu + * + */ +class WxPayNotifyReply extends WxPayDataBase +{ + /** + * + * 设置错误码 FAIL 或者 SUCCESS + * @param string + */ + public function SetReturn_code($return_code) + { + $this->values['return_code'] = $return_code; + } + + /** + * + * 获取错误码 FAIL 或者 SUCCESS + * @return string $return_code + */ + public function GetReturn_code() + { + return $this->values['return_code']; + } + + /** + * + * 设置错误信息 + * @param string $return_code + */ + public function SetReturn_msg($return_msg) + { + $this->values['return_msg'] = $return_msg; + } + + /** + * + * 获取错误信息 + * @return string + */ + public function GetReturn_msg() + { + return $this->values['return_msg']; + } + + /** + * + * 设置返回参数 + * @param string $key + * @param string $value + */ + public function SetData($key, $value) + { + $this->values[$key] = $value; + } +} + +/** + * + * 统一下单输入对象 + * @author widyhu + * + */ +class WxPayUnifiedOrder extends WxPayDataBase +{ + /** + * 设置微信分配的公众账号ID + * @param string $value + **/ + public function SetAppid($value) + { + $this->values['appid'] = $value; + } + /** + * 获取微信分配的公众账号ID的值 + * @return 值 + **/ + public function GetAppid() + { + return $this->values['appid']; + } + /** + * 判断微信分配的公众账号ID是否存在 + * @return true 或 false + **/ + public function IsAppidSet() + { + return array_key_exists('appid', $this->values); + } + + + /** + * 设置微信支付分配的商户号 + * @param string $value + **/ + public function SetMch_id($value) + { + $this->values['mch_id'] = $value; + } + /** + * 获取微信支付分配的商户号的值 + * @return 值 + **/ + public function GetMch_id() + { + return $this->values['mch_id']; + } + /** + * 判断微信支付分配的商户号是否存在 + * @return true 或 false + **/ + public function IsMch_idSet() + { + return array_key_exists('mch_id', $this->values); + } + + + /** + * 设置微信支付分配的终端设备号,商户自定义 + * @param string $value + **/ + public function SetDevice_info($value) + { + $this->values['device_info'] = $value; + } + /** + * 获取微信支付分配的终端设备号,商户自定义的值 + * @return 值 + **/ + public function GetDevice_info() + { + return $this->values['device_info']; + } + /** + * 判断微信支付分配的终端设备号,商户自定义是否存在 + * @return true 或 false + **/ + public function IsDevice_infoSet() + { + return array_key_exists('device_info', $this->values); + } + + + /** + * 设置随机字符串,不长于32位。推荐随机数生成算法 + * @param string $value + **/ + public function SetNonce_str($value) + { + $this->values['nonce_str'] = $value; + } + /** + * 获取随机字符串,不长于32位。推荐随机数生成算法的值 + * @return 值 + **/ + public function GetNonce_str() + { + return $this->values['nonce_str']; + } + /** + * 判断随机字符串,不长于32位。推荐随机数生成算法是否存在 + * @return true 或 false + **/ + public function IsNonce_strSet() + { + return array_key_exists('nonce_str', $this->values); + } + + /** + * 设置商品或支付单简要描述 + * @param string $value + **/ + public function SetBody($value) + { + $this->values['body'] = $value; + } + /** + * 获取商品或支付单简要描述的值 + * @return 值 + **/ + public function GetBody() + { + return $this->values['body']; + } + /** + * 判断商品或支付单简要描述是否存在 + * @return true 或 false + **/ + public function IsBodySet() + { + return array_key_exists('body', $this->values); + } + + + /** + * 设置商品名称明细列表 + * @param string $value + **/ + public function SetDetail($value) + { + $this->values['detail'] = $value; + } + /** + * 获取商品名称明细列表的值 + * @return 值 + **/ + public function GetDetail() + { + return $this->values['detail']; + } + /** + * 判断商品名称明细列表是否存在 + * @return true 或 false + **/ + public function IsDetailSet() + { + return array_key_exists('detail', $this->values); + } + + + /** + * 设置附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据 + * @param string $value + **/ + public function SetAttach($value) + { + $this->values['attach'] = $value; + } + /** + * 获取附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据的值 + * @return 值 + **/ + public function GetAttach() + { + return $this->values['attach']; + } + /** + * 判断附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据是否存在 + * @return true 或 false + **/ + public function IsAttachSet() + { + return array_key_exists('attach', $this->values); + } + + + /** + * 设置商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号 + * @param string $value + **/ + public function SetOut_trade_no($value) + { + $this->values['out_trade_no'] = $value; + } + /** + * 获取商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号的值 + * @return 值 + **/ + public function GetOut_trade_no() + { + return $this->values['out_trade_no']; + } + /** + * 判断商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号是否存在 + * @return true 或 false + **/ + public function IsOut_trade_noSet() + { + return array_key_exists('out_trade_no', $this->values); + } + + + /** + * 设置符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 + * @param string $value + **/ + public function SetFee_type($value) + { + $this->values['fee_type'] = $value; + } + /** + * 获取符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型的值 + * @return 值 + **/ + public function GetFee_type() + { + return $this->values['fee_type']; + } + /** + * 判断符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型是否存在 + * @return true 或 false + **/ + public function IsFee_typeSet() + { + return array_key_exists('fee_type', $this->values); + } + + + /** + * 设置订单总金额,只能为整数,详见支付金额 + * @param string $value + **/ + public function SetTotal_fee($value) + { + $this->values['total_fee'] = $value; + } + /** + * 获取订单总金额,只能为整数,详见支付金额的值 + * @return 值 + **/ + public function GetTotal_fee() + { + return $this->values['total_fee']; + } + /** + * 判断订单总金额,只能为整数,详见支付金额是否存在 + * @return true 或 false + **/ + public function IsTotal_feeSet() + { + return array_key_exists('total_fee', $this->values); + } + + + /** + * 设置APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。 + * @param string $value + **/ + public function SetSpbill_create_ip($value) + { + $this->values['spbill_create_ip'] = $value; + } + /** + * 获取APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。的值 + * @return 值 + **/ + public function GetSpbill_create_ip() + { + return $this->values['spbill_create_ip']; + } + /** + * 判断APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。是否存在 + * @return true 或 false + **/ + public function IsSpbill_create_ipSet() + { + return array_key_exists('spbill_create_ip', $this->values); + } + + + /** + * 设置订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 + * @param string $value + **/ + public function SetTime_start($value) + { + $this->values['time_start'] = $value; + } + /** + * 获取订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则的值 + * @return 值 + **/ + public function GetTime_start() + { + return $this->values['time_start']; + } + /** + * 判断订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则是否存在 + * @return true 或 false + **/ + public function IsTime_startSet() + { + return array_key_exists('time_start', $this->values); + } + + + /** + * 设置订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 + * @param string $value + **/ + public function SetTime_expire($value) + { + $this->values['time_expire'] = $value; + } + /** + * 获取订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则的值 + * @return 值 + **/ + public function GetTime_expire() + { + return $this->values['time_expire']; + } + /** + * 判断订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则是否存在 + * @return true 或 false + **/ + public function IsTime_expireSet() + { + return array_key_exists('time_expire', $this->values); + } + + + /** + * 设置商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠 + * @param string $value + **/ + public function SetGoods_tag($value) + { + $this->values['goods_tag'] = $value; + } + /** + * 获取商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠的值 + * @return 值 + **/ + public function GetGoods_tag() + { + return $this->values['goods_tag']; + } + /** + * 判断商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠是否存在 + * @return true 或 false + **/ + public function IsGoods_tagSet() + { + return array_key_exists('goods_tag', $this->values); + } + + + /** + * 设置接收微信支付异步通知回调地址 + * @param string $value + **/ + public function SetNotify_url($value) + { + $this->values['notify_url'] = $value; + } + /** + * 获取接收微信支付异步通知回调地址的值 + * @return 值 + **/ + public function GetNotify_url() + { + return $this->values['notify_url']; + } + /** + * 判断接收微信支付异步通知回调地址是否存在 + * @return true 或 false + **/ + public function IsNotify_urlSet() + { + return array_key_exists('notify_url', $this->values); + } + + + /** + * 设置取值如下:JSAPI,NATIVE,APP,详细说明见参数规定 + * @param string $value + **/ + public function SetTrade_type($value) + { + $this->values['trade_type'] = $value; + } + /** + * 获取取值如下:JSAPI,NATIVE,APP,详细说明见参数规定的值 + * @return 值 + **/ + public function GetTrade_type() + { + return $this->values['trade_type']; + } + /** + * 判断取值如下:JSAPI,NATIVE,APP,详细说明见参数规定是否存在 + * @return true 或 false + **/ + public function IsTrade_typeSet() + { + return array_key_exists('trade_type', $this->values); + } + + + /** + * 设置trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义。 + * @param string $value + **/ + public function SetProduct_id($value) + { + $this->values['product_id'] = $value; + } + /** + * 获取trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义。的值 + * @return 值 + **/ + public function GetProduct_id() + { + return $this->values['product_id']; + } + /** + * 判断trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义。是否存在 + * @return true 或 false + **/ + public function IsProduct_idSet() + { + return array_key_exists('product_id', $this->values); + } + + + /** + * 设置trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。下单前需要调用【网页授权获取用户信息】接口获取到用户的Openid。 + * @param string $value + **/ + public function SetOpenid($value) + { + $this->values['openid'] = $value; + } + /** + * 获取trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。下单前需要调用【网页授权获取用户信息】接口获取到用户的Openid。 的值 + * @return 值 + **/ + public function GetOpenid() + { + return $this->values['openid']; + } + /** + * 判断trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。下单前需要调用【网页授权获取用户信息】接口获取到用户的Openid。 是否存在 + * @return true 或 false + **/ + public function IsOpenidSet() + { + return array_key_exists('openid', $this->values); + } +} + +/** + * + * 订单查询输入对象 + * @author widyhu + * + */ +class WxPayOrderQuery extends WxPayDataBase +{ + /** + * 设置微信分配的公众账号ID + * @param string $value + **/ + public function SetAppid($value) + { + $this->values['appid'] = $value; + } + /** + * 获取微信分配的公众账号ID的值 + * @return 值 + **/ + public function GetAppid() + { + return $this->values['appid']; + } + /** + * 判断微信分配的公众账号ID是否存在 + * @return true 或 false + **/ + public function IsAppidSet() + { + return array_key_exists('appid', $this->values); + } + + + /** + * 设置微信支付分配的商户号 + * @param string $value + **/ + public function SetMch_id($value) + { + $this->values['mch_id'] = $value; + } + /** + * 获取微信支付分配的商户号的值 + * @return 值 + **/ + public function GetMch_id() + { + return $this->values['mch_id']; + } + /** + * 判断微信支付分配的商户号是否存在 + * @return true 或 false + **/ + public function IsMch_idSet() + { + return array_key_exists('mch_id', $this->values); + } + + + /** + * 设置微信的订单号,优先使用 + * @param string $value + **/ + public function SetTransaction_id($value) + { + $this->values['transaction_id'] = $value; + } + /** + * 获取微信的订单号,优先使用的值 + * @return 值 + **/ + public function GetTransaction_id() + { + return $this->values['transaction_id']; + } + /** + * 判断微信的订单号,优先使用是否存在 + * @return true 或 false + **/ + public function IsTransaction_idSet() + { + return array_key_exists('transaction_id', $this->values); + } + + + /** + * 设置商户系统内部的订单号,当没提供transaction_id时需要传这个。 + * @param string $value + **/ + public function SetOut_trade_no($value) + { + $this->values['out_trade_no'] = $value; + } + /** + * 获取商户系统内部的订单号,当没提供transaction_id时需要传这个。的值 + * @return 值 + **/ + public function GetOut_trade_no() + { + return $this->values['out_trade_no']; + } + /** + * 判断商户系统内部的订单号,当没提供transaction_id时需要传这个。是否存在 + * @return true 或 false + **/ + public function IsOut_trade_noSet() + { + return array_key_exists('out_trade_no', $this->values); + } + + + /** + * 设置随机字符串,不长于32位。推荐随机数生成算法 + * @param string $value + **/ + public function SetNonce_str($value) + { + $this->values['nonce_str'] = $value; + } + /** + * 获取随机字符串,不长于32位。推荐随机数生成算法的值 + * @return 值 + **/ + public function GetNonce_str() + { + return $this->values['nonce_str']; + } + /** + * 判断随机字符串,不长于32位。推荐随机数生成算法是否存在 + * @return true 或 false + **/ + public function IsNonce_strSet() + { + return array_key_exists('nonce_str', $this->values); + } +} + +/** + * + * 关闭订单输入对象 + * @author widyhu + * + */ +class WxPayCloseOrder extends WxPayDataBase +{ + /** + * 设置微信分配的公众账号ID + * @param string $value + **/ + public function SetAppid($value) + { + $this->values['appid'] = $value; + } + /** + * 获取微信分配的公众账号ID的值 + * @return 值 + **/ + public function GetAppid() + { + return $this->values['appid']; + } + /** + * 判断微信分配的公众账号ID是否存在 + * @return true 或 false + **/ + public function IsAppidSet() + { + return array_key_exists('appid', $this->values); + } + + + /** + * 设置微信支付分配的商户号 + * @param string $value + **/ + public function SetMch_id($value) + { + $this->values['mch_id'] = $value; + } + /** + * 获取微信支付分配的商户号的值 + * @return 值 + **/ + public function GetMch_id() + { + return $this->values['mch_id']; + } + /** + * 判断微信支付分配的商户号是否存在 + * @return true 或 false + **/ + public function IsMch_idSet() + { + return array_key_exists('mch_id', $this->values); + } + + + /** + * 设置商户系统内部的订单号 + * @param string $value + **/ + public function SetOut_trade_no($value) + { + $this->values['out_trade_no'] = $value; + } + /** + * 获取商户系统内部的订单号的值 + * @return 值 + **/ + public function GetOut_trade_no() + { + return $this->values['out_trade_no']; + } + /** + * 判断商户系统内部的订单号是否存在 + * @return true 或 false + **/ + public function IsOut_trade_noSet() + { + return array_key_exists('out_trade_no', $this->values); + } + + + /** + * 设置商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号 + * @param string $value + **/ + public function SetNonce_str($value) + { + $this->values['nonce_str'] = $value; + } + /** + * 获取商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号的值 + * @return 值 + **/ + public function GetNonce_str() + { + return $this->values['nonce_str']; + } + /** + * 判断商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号是否存在 + * @return true 或 false + **/ + public function IsNonce_strSet() + { + return array_key_exists('nonce_str', $this->values); + } +} + +/** + * + * 提交退款输入对象 + * @author widyhu + * + */ +class WxPayRefund extends WxPayDataBase +{ + /** + * 设置微信分配的公众账号ID + * @param string $value + **/ + public function SetAppid($value) + { + $this->values['appid'] = $value; + } + /** + * 获取微信分配的公众账号ID的值 + * @return 值 + **/ + public function GetAppid() + { + return $this->values['appid']; + } + /** + * 判断微信分配的公众账号ID是否存在 + * @return true 或 false + **/ + public function IsAppidSet() + { + return array_key_exists('appid', $this->values); + } + + + /** + * 设置微信支付分配的商户号 + * @param string $value + **/ + public function SetMch_id($value) + { + $this->values['mch_id'] = $value; + } + /** + * 获取微信支付分配的商户号的值 + * @return 值 + **/ + public function GetMch_id() + { + return $this->values['mch_id']; + } + /** + * 判断微信支付分配的商户号是否存在 + * @return true 或 false + **/ + public function IsMch_idSet() + { + return array_key_exists('mch_id', $this->values); + } + + + /** + * 设置微信支付分配的终端设备号,与下单一致 + * @param string $value + **/ + public function SetDevice_info($value) + { + $this->values['device_info'] = $value; + } + /** + * 获取微信支付分配的终端设备号,与下单一致的值 + * @return 值 + **/ + public function GetDevice_info() + { + return $this->values['device_info']; + } + /** + * 判断微信支付分配的终端设备号,与下单一致是否存在 + * @return true 或 false + **/ + public function IsDevice_infoSet() + { + return array_key_exists('device_info', $this->values); + } + + + /** + * 设置随机字符串,不长于32位。推荐随机数生成算法 + * @param string $value + **/ + public function SetNonce_str($value) + { + $this->values['nonce_str'] = $value; + } + /** + * 获取随机字符串,不长于32位。推荐随机数生成算法的值 + * @return 值 + **/ + public function GetNonce_str() + { + return $this->values['nonce_str']; + } + /** + * 判断随机字符串,不长于32位。推荐随机数生成算法是否存在 + * @return true 或 false + **/ + public function IsNonce_strSet() + { + return array_key_exists('nonce_str', $this->values); + } + + /** + * 设置微信订单号 + * @param string $value + **/ + public function SetTransaction_id($value) + { + $this->values['transaction_id'] = $value; + } + /** + * 获取微信订单号的值 + * @return 值 + **/ + public function GetTransaction_id() + { + return $this->values['transaction_id']; + } + /** + * 判断微信订单号是否存在 + * @return true 或 false + **/ + public function IsTransaction_idSet() + { + return array_key_exists('transaction_id', $this->values); + } + + + /** + * 设置商户系统内部的订单号,transaction_id、out_trade_no二选一,如果同时存在优先级:transaction_id> out_trade_no + * @param string $value + **/ + public function SetOut_trade_no($value) + { + $this->values['out_trade_no'] = $value; + } + /** + * 获取商户系统内部的订单号,transaction_id、out_trade_no二选一,如果同时存在优先级:transaction_id> out_trade_no的值 + * @return 值 + **/ + public function GetOut_trade_no() + { + return $this->values['out_trade_no']; + } + /** + * 判断商户系统内部的订单号,transaction_id、out_trade_no二选一,如果同时存在优先级:transaction_id> out_trade_no是否存在 + * @return true 或 false + **/ + public function IsOut_trade_noSet() + { + return array_key_exists('out_trade_no', $this->values); + } + + + /** + * 设置商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔 + * @param string $value + **/ + public function SetOut_refund_no($value) + { + $this->values['out_refund_no'] = $value; + } + /** + * 获取商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔的值 + * @return 值 + **/ + public function GetOut_refund_no() + { + return $this->values['out_refund_no']; + } + /** + * 判断商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔是否存在 + * @return true 或 false + **/ + public function IsOut_refund_noSet() + { + return array_key_exists('out_refund_no', $this->values); + } + + + /** + * 设置订单总金额,单位为分,只能为整数,详见支付金额 + * @param string $value + **/ + public function SetTotal_fee($value) + { + $this->values['total_fee'] = $value; + } + /** + * 获取订单总金额,单位为分,只能为整数,详见支付金额的值 + * @return 值 + **/ + public function GetTotal_fee() + { + return $this->values['total_fee']; + } + /** + * 判断订单总金额,单位为分,只能为整数,详见支付金额是否存在 + * @return true 或 false + **/ + public function IsTotal_feeSet() + { + return array_key_exists('total_fee', $this->values); + } + + + /** + * 设置退款总金额,订单总金额,单位为分,只能为整数,详见支付金额 + * @param string $value + **/ + public function SetRefund_fee($value) + { + $this->values['refund_fee'] = $value; + } + /** + * 获取退款总金额,订单总金额,单位为分,只能为整数,详见支付金额的值 + * @return 值 + **/ + public function GetRefund_fee() + { + return $this->values['refund_fee']; + } + /** + * 判断退款总金额,订单总金额,单位为分,只能为整数,详见支付金额是否存在 + * @return true 或 false + **/ + public function IsRefund_feeSet() + { + return array_key_exists('refund_fee', $this->values); + } + + + /** + * 设置货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 + * @param string $value + **/ + public function SetRefund_fee_type($value) + { + $this->values['refund_fee_type'] = $value; + } + /** + * 获取货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型的值 + * @return 值 + **/ + public function GetRefund_fee_type() + { + return $this->values['refund_fee_type']; + } + /** + * 判断货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型是否存在 + * @return true 或 false + **/ + public function IsRefund_fee_typeSet() + { + return array_key_exists('refund_fee_type', $this->values); + } + + + /** + * 设置操作员帐号, 默认为商户号 + * @param string $value + **/ + public function SetOp_user_id($value) + { + $this->values['op_user_id'] = $value; + } + /** + * 获取操作员帐号, 默认为商户号的值 + * @return 值 + **/ + public function GetOp_user_id() + { + return $this->values['op_user_id']; + } + /** + * 判断操作员帐号, 默认为商户号是否存在 + * @return true 或 false + **/ + public function IsOp_user_idSet() + { + return array_key_exists('op_user_id', $this->values); + } +} + +/** + * + * 退款查询输入对象 + * @author widyhu + * + */ +class WxPayRefundQuery extends WxPayDataBase +{ + /** + * 设置微信分配的公众账号ID + * @param string $value + **/ + public function SetAppid($value) + { + $this->values['appid'] = $value; + } + /** + * 获取微信分配的公众账号ID的值 + * @return 值 + **/ + public function GetAppid() + { + return $this->values['appid']; + } + /** + * 判断微信分配的公众账号ID是否存在 + * @return true 或 false + **/ + public function IsAppidSet() + { + return array_key_exists('appid', $this->values); + } + + + /** + * 设置微信支付分配的商户号 + * @param string $value + **/ + public function SetMch_id($value) + { + $this->values['mch_id'] = $value; + } + /** + * 获取微信支付分配的商户号的值 + * @return 值 + **/ + public function GetMch_id() + { + return $this->values['mch_id']; + } + /** + * 判断微信支付分配的商户号是否存在 + * @return true 或 false + **/ + public function IsMch_idSet() + { + return array_key_exists('mch_id', $this->values); + } + + + /** + * 设置微信支付分配的终端设备号 + * @param string $value + **/ + public function SetDevice_info($value) + { + $this->values['device_info'] = $value; + } + /** + * 获取微信支付分配的终端设备号的值 + * @return 值 + **/ + public function GetDevice_info() + { + return $this->values['device_info']; + } + /** + * 判断微信支付分配的终端设备号是否存在 + * @return true 或 false + **/ + public function IsDevice_infoSet() + { + return array_key_exists('device_info', $this->values); + } + + + /** + * 设置随机字符串,不长于32位。推荐随机数生成算法 + * @param string $value + **/ + public function SetNonce_str($value) + { + $this->values['nonce_str'] = $value; + } + /** + * 获取随机字符串,不长于32位。推荐随机数生成算法的值 + * @return 值 + **/ + public function GetNonce_str() + { + return $this->values['nonce_str']; + } + /** + * 判断随机字符串,不长于32位。推荐随机数生成算法是否存在 + * @return true 或 false + **/ + public function IsNonce_strSet() + { + return array_key_exists('nonce_str', $this->values); + } + + /** + * 设置微信订单号 + * @param string $value + **/ + public function SetTransaction_id($value) + { + $this->values['transaction_id'] = $value; + } + /** + * 获取微信订单号的值 + * @return 值 + **/ + public function GetTransaction_id() + { + return $this->values['transaction_id']; + } + /** + * 判断微信订单号是否存在 + * @return true 或 false + **/ + public function IsTransaction_idSet() + { + return array_key_exists('transaction_id', $this->values); + } + + + /** + * 设置商户系统内部的订单号 + * @param string $value + **/ + public function SetOut_trade_no($value) + { + $this->values['out_trade_no'] = $value; + } + /** + * 获取商户系统内部的订单号的值 + * @return 值 + **/ + public function GetOut_trade_no() + { + return $this->values['out_trade_no']; + } + /** + * 判断商户系统内部的订单号是否存在 + * @return true 或 false + **/ + public function IsOut_trade_noSet() + { + return array_key_exists('out_trade_no', $this->values); + } + + + /** + * 设置商户退款单号 + * @param string $value + **/ + public function SetOut_refund_no($value) + { + $this->values['out_refund_no'] = $value; + } + /** + * 获取商户退款单号的值 + * @return 值 + **/ + public function GetOut_refund_no() + { + return $this->values['out_refund_no']; + } + /** + * 判断商户退款单号是否存在 + * @return true 或 false + **/ + public function IsOut_refund_noSet() + { + return array_key_exists('out_refund_no', $this->values); + } + + + /** + * 设置微信退款单号refund_id、out_refund_no、out_trade_no、transaction_id四个参数必填一个,如果同时存在优先级为:refund_id>out_refund_no>transaction_id>out_trade_no + * @param string $value + **/ + public function SetRefund_id($value) + { + $this->values['refund_id'] = $value; + } + /** + * 获取微信退款单号refund_id、out_refund_no、out_trade_no、transaction_id四个参数必填一个,如果同时存在优先级为:refund_id>out_refund_no>transaction_id>out_trade_no的值 + * @return 值 + **/ + public function GetRefund_id() + { + return $this->values['refund_id']; + } + /** + * 判断微信退款单号refund_id、out_refund_no、out_trade_no、transaction_id四个参数必填一个,如果同时存在优先级为:refund_id>out_refund_no>transaction_id>out_trade_no是否存在 + * @return true 或 false + **/ + public function IsRefund_idSet() + { + return array_key_exists('refund_id', $this->values); + } +} + +/** + * + * 下载对账单输入对象 + * @author widyhu + * + */ +class WxPayDownloadBill extends WxPayDataBase +{ + /** + * 设置微信分配的公众账号ID + * @param string $value + **/ + public function SetAppid($value) + { + $this->values['appid'] = $value; + } + /** + * 获取微信分配的公众账号ID的值 + * @return 值 + **/ + public function GetAppid() + { + return $this->values['appid']; + } + /** + * 判断微信分配的公众账号ID是否存在 + * @return true 或 false + **/ + public function IsAppidSet() + { + return array_key_exists('appid', $this->values); + } + + + /** + * 设置微信支付分配的商户号 + * @param string $value + **/ + public function SetMch_id($value) + { + $this->values['mch_id'] = $value; + } + /** + * 获取微信支付分配的商户号的值 + * @return 值 + **/ + public function GetMch_id() + { + return $this->values['mch_id']; + } + /** + * 判断微信支付分配的商户号是否存在 + * @return true 或 false + **/ + public function IsMch_idSet() + { + return array_key_exists('mch_id', $this->values); + } + + + /** + * 设置微信支付分配的终端设备号,填写此字段,只下载该设备号的对账单 + * @param string $value + **/ + public function SetDevice_info($value) + { + $this->values['device_info'] = $value; + } + /** + * 获取微信支付分配的终端设备号,填写此字段,只下载该设备号的对账单的值 + * @return 值 + **/ + public function GetDevice_info() + { + return $this->values['device_info']; + } + /** + * 判断微信支付分配的终端设备号,填写此字段,只下载该设备号的对账单是否存在 + * @return true 或 false + **/ + public function IsDevice_infoSet() + { + return array_key_exists('device_info', $this->values); + } + + + /** + * 设置随机字符串,不长于32位。推荐随机数生成算法 + * @param string $value + **/ + public function SetNonce_str($value) + { + $this->values['nonce_str'] = $value; + } + /** + * 获取随机字符串,不长于32位。推荐随机数生成算法的值 + * @return 值 + **/ + public function GetNonce_str() + { + return $this->values['nonce_str']; + } + /** + * 判断随机字符串,不长于32位。推荐随机数生成算法是否存在 + * @return true 或 false + **/ + public function IsNonce_strSet() + { + return array_key_exists('nonce_str', $this->values); + } + + /** + * 设置下载对账单的日期,格式:20140603 + * @param string $value + **/ + public function SetBill_date($value) + { + $this->values['bill_date'] = $value; + } + /** + * 获取下载对账单的日期,格式:20140603的值 + * @return 值 + **/ + public function GetBill_date() + { + return $this->values['bill_date']; + } + /** + * 判断下载对账单的日期,格式:20140603是否存在 + * @return true 或 false + **/ + public function IsBill_dateSet() + { + return array_key_exists('bill_date', $this->values); + } + + + /** + * 设置ALL,返回当日所有订单信息,默认值SUCCESS,返回当日成功支付的订单REFUND,返回当日退款订单REVOKED,已撤销的订单 + * @param string $value + **/ + public function SetBill_type($value) + { + $this->values['bill_type'] = $value; + } + /** + * 获取ALL,返回当日所有订单信息,默认值SUCCESS,返回当日成功支付的订单REFUND,返回当日退款订单REVOKED,已撤销的订单的值 + * @return 值 + **/ + public function GetBill_type() + { + return $this->values['bill_type']; + } + /** + * 判断ALL,返回当日所有订单信息,默认值SUCCESS,返回当日成功支付的订单REFUND,返回当日退款订单REVOKED,已撤销的订单是否存在 + * @return true 或 false + **/ + public function IsBill_typeSet() + { + return array_key_exists('bill_type', $this->values); + } +} + +/** + * + * 测速上报输入对象 + * @author widyhu + * + */ +class WxPayReport extends WxPayDataBase +{ + /** + * 设置微信分配的公众账号ID + * @param string $value + **/ + public function SetAppid($value) + { + $this->values['appid'] = $value; + } + /** + * 获取微信分配的公众账号ID的值 + * @return 值 + **/ + public function GetAppid() + { + return $this->values['appid']; + } + /** + * 判断微信分配的公众账号ID是否存在 + * @return true 或 false + **/ + public function IsAppidSet() + { + return array_key_exists('appid', $this->values); + } + + + /** + * 设置微信支付分配的商户号 + * @param string $value + **/ + public function SetMch_id($value) + { + $this->values['mch_id'] = $value; + } + /** + * 获取微信支付分配的商户号的值 + * @return 值 + **/ + public function GetMch_id() + { + return $this->values['mch_id']; + } + /** + * 判断微信支付分配的商户号是否存在 + * @return true 或 false + **/ + public function IsMch_idSet() + { + return array_key_exists('mch_id', $this->values); + } + + + /** + * 设置微信支付分配的终端设备号,商户自定义 + * @param string $value + **/ + public function SetDevice_info($value) + { + $this->values['device_info'] = $value; + } + /** + * 获取微信支付分配的终端设备号,商户自定义的值 + * @return 值 + **/ + public function GetDevice_info() + { + return $this->values['device_info']; + } + /** + * 判断微信支付分配的终端设备号,商户自定义是否存在 + * @return true 或 false + **/ + public function IsDevice_infoSet() + { + return array_key_exists('device_info', $this->values); + } + + + /** + * 设置随机字符串,不长于32位。推荐随机数生成算法 + * @param string $value + **/ + public function SetNonce_str($value) + { + $this->values['nonce_str'] = $value; + } + /** + * 获取随机字符串,不长于32位。推荐随机数生成算法的值 + * @return 值 + **/ + public function GetNonce_str() + { + return $this->values['nonce_str']; + } + /** + * 判断随机字符串,不长于32位。推荐随机数生成算法是否存在 + * @return true 或 false + **/ + public function IsNonce_strSet() + { + return array_key_exists('nonce_str', $this->values); + } + + + /** + * 设置上报对应的接口的完整URL,类似:https://api.mch.weixin.qq.com/pay/unifiedorder对于被扫支付,为更好的和商户共同分析一次业务行为的整体耗时情况,对于两种接入模式,请都在门店侧对一次被扫行为进行一次单独的整体上报,上报URL指定为:https://api.mch.weixin.qq.com/pay/micropay/total关于两种接入模式具体可参考本文档章节:被扫支付商户接入模式其它接口调用仍然按照调用一次,上报一次来进行。 + * @param string $value + **/ + public function SetInterface_url($value) + { + $this->values['interface_url'] = $value; + } + /** + * 获取上报对应的接口的完整URL,类似:https://api.mch.weixin.qq.com/pay/unifiedorder对于被扫支付,为更好的和商户共同分析一次业务行为的整体耗时情况,对于两种接入模式,请都在门店侧对一次被扫行为进行一次单独的整体上报,上报URL指定为:https://api.mch.weixin.qq.com/pay/micropay/total关于两种接入模式具体可参考本文档章节:被扫支付商户接入模式其它接口调用仍然按照调用一次,上报一次来进行。的值 + * @return 值 + **/ + public function GetInterface_url() + { + return $this->values['interface_url']; + } + /** + * 判断上报对应的接口的完整URL,类似:https://api.mch.weixin.qq.com/pay/unifiedorder对于被扫支付,为更好的和商户共同分析一次业务行为的整体耗时情况,对于两种接入模式,请都在门店侧对一次被扫行为进行一次单独的整体上报,上报URL指定为:https://api.mch.weixin.qq.com/pay/micropay/total关于两种接入模式具体可参考本文档章节:被扫支付商户接入模式其它接口调用仍然按照调用一次,上报一次来进行。是否存在 + * @return true 或 false + **/ + public function IsInterface_urlSet() + { + return array_key_exists('interface_url', $this->values); + } + + + /** + * 设置接口耗时情况,单位为毫秒 + * @param string $value + **/ + public function SetExecute_time_($value) + { + $this->values['execute_time_'] = $value; + } + /** + * 获取接口耗时情况,单位为毫秒的值 + * @return 值 + **/ + public function GetExecute_time_() + { + return $this->values['execute_time_']; + } + /** + * 判断接口耗时情况,单位为毫秒是否存在 + * @return true 或 false + **/ + public function IsExecute_time_Set() + { + return array_key_exists('execute_time_', $this->values); + } + + + /** + * 设置SUCCESS/FAIL此字段是通信标识,非交易标识,交易是否成功需要查看trade_state来判断 + * @param string $value + **/ + public function SetReturn_code($value) + { + $this->values['return_code'] = $value; + } + /** + * 获取SUCCESS/FAIL此字段是通信标识,非交易标识,交易是否成功需要查看trade_state来判断的值 + * @return 值 + **/ + public function GetReturn_code() + { + return $this->values['return_code']; + } + /** + * 判断SUCCESS/FAIL此字段是通信标识,非交易标识,交易是否成功需要查看trade_state来判断是否存在 + * @return true 或 false + **/ + public function IsReturn_codeSet() + { + return array_key_exists('return_code', $this->values); + } + + + /** + * 设置返回信息,如非空,为错误原因签名失败参数格式校验错误 + * @param string $value + **/ + public function SetReturn_msg($value) + { + $this->values['return_msg'] = $value; + } + /** + * 获取返回信息,如非空,为错误原因签名失败参数格式校验错误的值 + * @return 值 + **/ + public function GetReturn_msg() + { + return $this->values['return_msg']; + } + /** + * 判断返回信息,如非空,为错误原因签名失败参数格式校验错误是否存在 + * @return true 或 false + **/ + public function IsReturn_msgSet() + { + return array_key_exists('return_msg', $this->values); + } + + + /** + * 设置SUCCESS/FAIL + * @param string $value + **/ + public function SetResult_code($value) + { + $this->values['result_code'] = $value; + } + /** + * 获取SUCCESS/FAIL的值 + * @return 值 + **/ + public function GetResult_code() + { + return $this->values['result_code']; + } + /** + * 判断SUCCESS/FAIL是否存在 + * @return true 或 false + **/ + public function IsResult_codeSet() + { + return array_key_exists('result_code', $this->values); + } + + + /** + * 设置ORDERNOTEXIST—订单不存在SYSTEMERROR—系统错误 + * @param string $value + **/ + public function SetErr_code($value) + { + $this->values['err_code'] = $value; + } + /** + * 获取ORDERNOTEXIST—订单不存在SYSTEMERROR—系统错误的值 + * @return 值 + **/ + public function GetErr_code() + { + return $this->values['err_code']; + } + /** + * 判断ORDERNOTEXIST—订单不存在SYSTEMERROR—系统错误是否存在 + * @return true 或 false + **/ + public function IsErr_codeSet() + { + return array_key_exists('err_code', $this->values); + } + + + /** + * 设置结果信息描述 + * @param string $value + **/ + public function SetErr_code_des($value) + { + $this->values['err_code_des'] = $value; + } + /** + * 获取结果信息描述的值 + * @return 值 + **/ + public function GetErr_code_des() + { + return $this->values['err_code_des']; + } + /** + * 判断结果信息描述是否存在 + * @return true 或 false + **/ + public function IsErr_code_desSet() + { + return array_key_exists('err_code_des', $this->values); + } + + + /** + * 设置商户系统内部的订单号,商户可以在上报时提供相关商户订单号方便微信支付更好的提高服务质量。 + * @param string $value + **/ + public function SetOut_trade_no($value) + { + $this->values['out_trade_no'] = $value; + } + /** + * 获取商户系统内部的订单号,商户可以在上报时提供相关商户订单号方便微信支付更好的提高服务质量。 的值 + * @return 值 + **/ + public function GetOut_trade_no() + { + return $this->values['out_trade_no']; + } + /** + * 判断商户系统内部的订单号,商户可以在上报时提供相关商户订单号方便微信支付更好的提高服务质量。 是否存在 + * @return true 或 false + **/ + public function IsOut_trade_noSet() + { + return array_key_exists('out_trade_no', $this->values); + } + + + /** + * 设置发起接口调用时的机器IP + * @param string $value + **/ + public function SetUser_ip($value) + { + $this->values['user_ip'] = $value; + } + /** + * 获取发起接口调用时的机器IP 的值 + * @return 值 + **/ + public function GetUser_ip() + { + return $this->values['user_ip']; + } + /** + * 判断发起接口调用时的机器IP 是否存在 + * @return true 或 false + **/ + public function IsUser_ipSet() + { + return array_key_exists('user_ip', $this->values); + } + + + /** + * 设置系统时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 + * @param string $value + **/ + public function SetTime($value) + { + $this->values['time'] = $value; + } + /** + * 获取系统时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则的值 + * @return 值 + **/ + public function GetTime() + { + return $this->values['time']; + } + /** + * 判断系统时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则是否存在 + * @return true 或 false + **/ + public function IsTimeSet() + { + return array_key_exists('time', $this->values); + } +} + +/** + * + * 短链转换输入对象 + * @author widyhu + * + */ +class WxPayShortUrl extends WxPayDataBase +{ + /** + * 设置微信分配的公众账号ID + * @param string $value + **/ + public function SetAppid($value) + { + $this->values['appid'] = $value; + } + /** + * 获取微信分配的公众账号ID的值 + * @return 值 + **/ + public function GetAppid() + { + return $this->values['appid']; + } + /** + * 判断微信分配的公众账号ID是否存在 + * @return true 或 false + **/ + public function IsAppidSet() + { + return array_key_exists('appid', $this->values); + } + + + /** + * 设置微信支付分配的商户号 + * @param string $value + **/ + public function SetMch_id($value) + { + $this->values['mch_id'] = $value; + } + /** + * 获取微信支付分配的商户号的值 + * @return 值 + **/ + public function GetMch_id() + { + return $this->values['mch_id']; + } + /** + * 判断微信支付分配的商户号是否存在 + * @return true 或 false + **/ + public function IsMch_idSet() + { + return array_key_exists('mch_id', $this->values); + } + + + /** + * 设置需要转换的URL,签名用原串,传输需URL encode + * @param string $value + **/ + public function SetLong_url($value) + { + $this->values['long_url'] = $value; + } + /** + * 获取需要转换的URL,签名用原串,传输需URL encode的值 + * @return 值 + **/ + public function GetLong_url() + { + return $this->values['long_url']; + } + /** + * 判断需要转换的URL,签名用原串,传输需URL encode是否存在 + * @return true 或 false + **/ + public function IsLong_urlSet() + { + return array_key_exists('long_url', $this->values); + } + + + /** + * 设置随机字符串,不长于32位。推荐随机数生成算法 + * @param string $value + **/ + public function SetNonce_str($value) + { + $this->values['nonce_str'] = $value; + } + /** + * 获取随机字符串,不长于32位。推荐随机数生成算法的值 + * @return 值 + **/ + public function GetNonce_str() + { + return $this->values['nonce_str']; + } + /** + * 判断随机字符串,不长于32位。推荐随机数生成算法是否存在 + * @return true 或 false + **/ + public function IsNonce_strSet() + { + return array_key_exists('nonce_str', $this->values); + } +} + +/** + * + * 提交被扫输入对象 + * @author widyhu + * + */ +class WxPayMicroPay extends WxPayDataBase +{ + /** + * 设置微信分配的公众账号ID + * @param string $value + **/ + public function SetAppid($value) + { + $this->values['appid'] = $value; + } + /** + * 获取微信分配的公众账号ID的值 + * @return 值 + **/ + public function GetAppid() + { + return $this->values['appid']; + } + /** + * 判断微信分配的公众账号ID是否存在 + * @return true 或 false + **/ + public function IsAppidSet() + { + return array_key_exists('appid', $this->values); + } + + + /** + * 设置微信支付分配的商户号 + * @param string $value + **/ + public function SetMch_id($value) + { + $this->values['mch_id'] = $value; + } + /** + * 获取微信支付分配的商户号的值 + * @return 值 + **/ + public function GetMch_id() + { + return $this->values['mch_id']; + } + /** + * 判断微信支付分配的商户号是否存在 + * @return true 或 false + **/ + public function IsMch_idSet() + { + return array_key_exists('mch_id', $this->values); + } + + + /** + * 设置终端设备号(商户自定义,如门店编号) + * @param string $value + **/ + public function SetDevice_info($value) + { + $this->values['device_info'] = $value; + } + /** + * 获取终端设备号(商户自定义,如门店编号)的值 + * @return 值 + **/ + public function GetDevice_info() + { + return $this->values['device_info']; + } + /** + * 判断终端设备号(商户自定义,如门店编号)是否存在 + * @return true 或 false + **/ + public function IsDevice_infoSet() + { + return array_key_exists('device_info', $this->values); + } + + + /** + * 设置随机字符串,不长于32位。推荐随机数生成算法 + * @param string $value + **/ + public function SetNonce_str($value) + { + $this->values['nonce_str'] = $value; + } + /** + * 获取随机字符串,不长于32位。推荐随机数生成算法的值 + * @return 值 + **/ + public function GetNonce_str() + { + return $this->values['nonce_str']; + } + /** + * 判断随机字符串,不长于32位。推荐随机数生成算法是否存在 + * @return true 或 false + **/ + public function IsNonce_strSet() + { + return array_key_exists('nonce_str', $this->values); + } + + /** + * 设置商品或支付单简要描述 + * @param string $value + **/ + public function SetBody($value) + { + $this->values['body'] = $value; + } + /** + * 获取商品或支付单简要描述的值 + * @return 值 + **/ + public function GetBody() + { + return $this->values['body']; + } + /** + * 判断商品或支付单简要描述是否存在 + * @return true 或 false + **/ + public function IsBodySet() + { + return array_key_exists('body', $this->values); + } + + + /** + * 设置商品名称明细列表 + * @param string $value + **/ + public function SetDetail($value) + { + $this->values['detail'] = $value; + } + /** + * 获取商品名称明细列表的值 + * @return 值 + **/ + public function GetDetail() + { + return $this->values['detail']; + } + /** + * 判断商品名称明细列表是否存在 + * @return true 或 false + **/ + public function IsDetailSet() + { + return array_key_exists('detail', $this->values); + } + + + /** + * 设置附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据 + * @param string $value + **/ + public function SetAttach($value) + { + $this->values['attach'] = $value; + } + /** + * 获取附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据的值 + * @return 值 + **/ + public function GetAttach() + { + return $this->values['attach']; + } + /** + * 判断附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据是否存在 + * @return true 或 false + **/ + public function IsAttachSet() + { + return array_key_exists('attach', $this->values); + } + + + /** + * 设置商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号 + * @param string $value + **/ + public function SetOut_trade_no($value) + { + $this->values['out_trade_no'] = $value; + } + /** + * 获取商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号的值 + * @return 值 + **/ + public function GetOut_trade_no() + { + return $this->values['out_trade_no']; + } + /** + * 判断商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号是否存在 + * @return true 或 false + **/ + public function IsOut_trade_noSet() + { + return array_key_exists('out_trade_no', $this->values); + } + + + /** + * 设置订单总金额,单位为分,只能为整数,详见支付金额 + * @param string $value + **/ + public function SetTotal_fee($value) + { + $this->values['total_fee'] = $value; + } + /** + * 获取订单总金额,单位为分,只能为整数,详见支付金额的值 + * @return 值 + **/ + public function GetTotal_fee() + { + return $this->values['total_fee']; + } + /** + * 判断订单总金额,单位为分,只能为整数,详见支付金额是否存在 + * @return true 或 false + **/ + public function IsTotal_feeSet() + { + return array_key_exists('total_fee', $this->values); + } + + + /** + * 设置符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 + * @param string $value + **/ + public function SetFee_type($value) + { + $this->values['fee_type'] = $value; + } + /** + * 获取符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型的值 + * @return 值 + **/ + public function GetFee_type() + { + return $this->values['fee_type']; + } + /** + * 判断符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型是否存在 + * @return true 或 false + **/ + public function IsFee_typeSet() + { + return array_key_exists('fee_type', $this->values); + } + + + /** + * 设置调用微信支付API的机器IP + * @param string $value + **/ + public function SetSpbill_create_ip($value) + { + $this->values['spbill_create_ip'] = $value; + } + /** + * 获取调用微信支付API的机器IP 的值 + * @return 值 + **/ + public function GetSpbill_create_ip() + { + return $this->values['spbill_create_ip']; + } + /** + * 判断调用微信支付API的机器IP 是否存在 + * @return true 或 false + **/ + public function IsSpbill_create_ipSet() + { + return array_key_exists('spbill_create_ip', $this->values); + } + + + /** + * 设置订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。详见时间规则 + * @param string $value + **/ + public function SetTime_start($value) + { + $this->values['time_start'] = $value; + } + /** + * 获取订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。详见时间规则的值 + * @return 值 + **/ + public function GetTime_start() + { + return $this->values['time_start']; + } + /** + * 判断订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。详见时间规则是否存在 + * @return true 或 false + **/ + public function IsTime_startSet() + { + return array_key_exists('time_start', $this->values); + } + + + /** + * 设置订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。详见时间规则 + * @param string $value + **/ + public function SetTime_expire($value) + { + $this->values['time_expire'] = $value; + } + /** + * 获取订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。详见时间规则的值 + * @return 值 + **/ + public function GetTime_expire() + { + return $this->values['time_expire']; + } + /** + * 判断订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。详见时间规则是否存在 + * @return true 或 false + **/ + public function IsTime_expireSet() + { + return array_key_exists('time_expire', $this->values); + } + + + /** + * 设置商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠 + * @param string $value + **/ + public function SetGoods_tag($value) + { + $this->values['goods_tag'] = $value; + } + /** + * 获取商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠的值 + * @return 值 + **/ + public function GetGoods_tag() + { + return $this->values['goods_tag']; + } + /** + * 判断商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠是否存在 + * @return true 或 false + **/ + public function IsGoods_tagSet() + { + return array_key_exists('goods_tag', $this->values); + } + + + /** + * 设置扫码支付授权码,设备读取用户微信中的条码或者二维码信息 + * @param string $value + **/ + public function SetAuth_code($value) + { + $this->values['auth_code'] = $value; + } + /** + * 获取扫码支付授权码,设备读取用户微信中的条码或者二维码信息的值 + * @return 值 + **/ + public function GetAuth_code() + { + return $this->values['auth_code']; + } + /** + * 判断扫码支付授权码,设备读取用户微信中的条码或者二维码信息是否存在 + * @return true 或 false + **/ + public function IsAuth_codeSet() + { + return array_key_exists('auth_code', $this->values); + } +} + +/** + * + * 撤销输入对象 + * @author widyhu + * + */ +class WxPayReverse extends WxPayDataBase +{ + /** + * 设置微信分配的公众账号ID + * @param string $value + **/ + public function SetAppid($value) + { + $this->values['appid'] = $value; + } + /** + * 获取微信分配的公众账号ID的值 + * @return 值 + **/ + public function GetAppid() + { + return $this->values['appid']; + } + /** + * 判断微信分配的公众账号ID是否存在 + * @return true 或 false + **/ + public function IsAppidSet() + { + return array_key_exists('appid', $this->values); + } + + + /** + * 设置微信支付分配的商户号 + * @param string $value + **/ + public function SetMch_id($value) + { + $this->values['mch_id'] = $value; + } + /** + * 获取微信支付分配的商户号的值 + * @return 值 + **/ + public function GetMch_id() + { + return $this->values['mch_id']; + } + /** + * 判断微信支付分配的商户号是否存在 + * @return true 或 false + **/ + public function IsMch_idSet() + { + return array_key_exists('mch_id', $this->values); + } + + + /** + * 设置微信的订单号,优先使用 + * @param string $value + **/ + public function SetTransaction_id($value) + { + $this->values['transaction_id'] = $value; + } + /** + * 获取微信的订单号,优先使用的值 + * @return 值 + **/ + public function GetTransaction_id() + { + return $this->values['transaction_id']; + } + /** + * 判断微信的订单号,优先使用是否存在 + * @return true 或 false + **/ + public function IsTransaction_idSet() + { + return array_key_exists('transaction_id', $this->values); + } + + + /** + * 设置商户系统内部的订单号,transaction_id、out_trade_no二选一,如果同时存在优先级:transaction_id> out_trade_no + * @param string $value + **/ + public function SetOut_trade_no($value) + { + $this->values['out_trade_no'] = $value; + } + /** + * 获取商户系统内部的订单号,transaction_id、out_trade_no二选一,如果同时存在优先级:transaction_id> out_trade_no的值 + * @return 值 + **/ + public function GetOut_trade_no() + { + return $this->values['out_trade_no']; + } + /** + * 判断商户系统内部的订单号,transaction_id、out_trade_no二选一,如果同时存在优先级:transaction_id> out_trade_no是否存在 + * @return true 或 false + **/ + public function IsOut_trade_noSet() + { + return array_key_exists('out_trade_no', $this->values); + } + + + /** + * 设置随机字符串,不长于32位。推荐随机数生成算法 + * @param string $value + **/ + public function SetNonce_str($value) + { + $this->values['nonce_str'] = $value; + } + /** + * 获取随机字符串,不长于32位。推荐随机数生成算法的值 + * @return 值 + **/ + public function GetNonce_str() + { + return $this->values['nonce_str']; + } + /** + * 判断随机字符串,不长于32位。推荐随机数生成算法是否存在 + * @return true 或 false + **/ + public function IsNonce_strSet() + { + return array_key_exists('nonce_str', $this->values); + } +} + +/** + * + * 提交JSAPI输入对象 + * @author widyhu + * + */ +class WxPayJsApiPay extends WxPayDataBase +{ + /** + * 设置微信分配的公众账号ID + * @param string $value + **/ + public function SetAppid($value) + { + $this->values['appId'] = $value; + } + /** + * 获取微信分配的公众账号ID的值 + * @return 值 + **/ + public function GetAppid() + { + return $this->values['appId']; + } + /** + * 判断微信分配的公众账号ID是否存在 + * @return true 或 false + **/ + public function IsAppidSet() + { + return array_key_exists('appId', $this->values); + } + + + /** + * 设置支付时间戳 + * @param string $value + **/ + public function SetTimeStamp($value) + { + $this->values['timeStamp'] = $value; + } + /** + * 获取支付时间戳的值 + * @return 值 + **/ + public function GetTimeStamp() + { + return $this->values['timeStamp']; + } + /** + * 判断支付时间戳是否存在 + * @return true 或 false + **/ + public function IsTimeStampSet() + { + return array_key_exists('timeStamp', $this->values); + } + + /** + * 随机字符串 + * @param string $value + **/ + public function SetNonceStr($value) + { + $this->values['nonceStr'] = $value; + } + /** + * 获取notify随机字符串值 + * @return 值 + **/ + public function GetReturn_code() + { + return $this->values['nonceStr']; + } + /** + * 判断随机字符串是否存在 + * @return true 或 false + **/ + public function IsReturn_codeSet() + { + return array_key_exists('nonceStr', $this->values); + } + + + /** + * 设置订单详情扩展字符串 + * @param string $value + **/ + public function SetPackage($value) + { + $this->values['package'] = $value; + } + /** + * 获取订单详情扩展字符串的值 + * @return 值 + **/ + public function GetPackage() + { + return $this->values['package']; + } + /** + * 判断订单详情扩展字符串是否存在 + * @return true 或 false + **/ + public function IsPackageSet() + { + return array_key_exists('package', $this->values); + } + + /** + * 设置签名方式 + * @param string $value + **/ + public function SetSignType($value) + { + $this->values['signType'] = $value; + } + /** + * 获取签名方式 + * @return 值 + **/ + public function GetSignType() + { + return $this->values['signType']; + } + /** + * 判断签名方式是否存在 + * @return true 或 false + **/ + public function IsSignTypeSet() + { + return array_key_exists('signType', $this->values); + } + + /** + * 设置签名方式 + * @param string $value + **/ + public function SetPaySign($value) + { + $this->values['paySign'] = $value; + } + /** + * 获取签名方式 + * @return 值 + **/ + public function GetPaySign() + { + return $this->values['paySign']; + } + /** + * 判断签名方式是否存在 + * @return true 或 false + **/ + public function IsPaySignSet() + { + return array_key_exists('paySign', $this->values); + } +} + +/** + * + * 扫码支付模式一生成二维码参数 + * @author widyhu + * + */ +class WxPayBizPayUrl extends WxPayDataBase +{ + /** + * 设置微信分配的公众账号ID + * @param string $value + **/ + public function SetAppid($value) + { + $this->values['appid'] = $value; + } + /** + * 获取微信分配的公众账号ID的值 + * @return 值 + **/ + public function GetAppid() + { + return $this->values['appid']; + } + /** + * 判断微信分配的公众账号ID是否存在 + * @return true 或 false + **/ + public function IsAppidSet() + { + return array_key_exists('appid', $this->values); + } + + + /** + * 设置微信支付分配的商户号 + * @param string $value + **/ + public function SetMch_id($value) + { + $this->values['mch_id'] = $value; + } + /** + * 获取微信支付分配的商户号的值 + * @return 值 + **/ + public function GetMch_id() + { + return $this->values['mch_id']; + } + /** + * 判断微信支付分配的商户号是否存在 + * @return true 或 false + **/ + public function IsMch_idSet() + { + return array_key_exists('mch_id', $this->values); + } + + /** + * 设置支付时间戳 + * @param string $value + **/ + public function SetTime_stamp($value) + { + $this->values['time_stamp'] = $value; + } + /** + * 获取支付时间戳的值 + * @return 值 + **/ + public function GetTime_stamp() + { + return $this->values['time_stamp']; + } + /** + * 判断支付时间戳是否存在 + * @return true 或 false + **/ + public function IsTime_stampSet() + { + return array_key_exists('time_stamp', $this->values); + } + + /** + * 设置随机字符串 + * @param string $value + **/ + public function SetNonce_str($value) + { + $this->values['nonce_str'] = $value; + } + /** + * 获取随机字符串的值 + * @return 值 + **/ + public function GetNonce_str() + { + return $this->values['nonce_str']; + } + /** + * 判断随机字符串是否存在 + * @return true 或 false + **/ + public function IsNonce_strSet() + { + return array_key_exists('nonce_str', $this->values); + } + + /** + * 设置商品ID + * @param string $value + **/ + public function SetProduct_id($value) + { + $this->values['product_id'] = $value; + } + /** + * 获取商品ID的值 + * @return 值 + **/ + public function GetProduct_id() + { + return $this->values['product_id']; + } + /** + * 判断商品ID是否存在 + * @return true 或 false + **/ + public function IsProduct_idSet() + { + return array_key_exists('product_id', $this->values); + } +} diff --git a/weixinpay/lib/WxPay.Exception.php b/weixinpay/lib/WxPay.Exception.php new file mode 100644 index 0000000..dbe0853 --- /dev/null +++ b/weixinpay/lib/WxPay.Exception.php @@ -0,0 +1,13 @@ +getMessage(); + } +} diff --git a/weixinpay/lib/WxPay.Notify.php b/weixinpay/lib/WxPay.Notify.php new file mode 100644 index 0000000..b88156b --- /dev/null +++ b/weixinpay/lib/WxPay.Notify.php @@ -0,0 +1,93 @@ +SetReturn_code("FAIL"); + $this->SetReturn_msg($msg); + $this->ReplyNotify(false); + return; + } else { + file_put_contents('./weixinQuery.txt','true',FILE_APPEND); + //该分支在成功回调到NotifyCallBack方法,处理完成之后流程 + $this->SetReturn_code("SUCCESS"); + $this->SetReturn_msg("OK"); + } + $this->ReplyNotify($needSign); + } + /** + * + * 回调方法入口,子类可重写该方法 + * 注意: + * 1、微信回调超时时间为2s,建议用户使用异步处理流程,确认成功之后立刻回复微信服务器 + * 2、微信服务器在调用失败或者接到回包为非确认包的时候,会发起重试,需确保你的回调是可以重入 + * @param array $data 回调解释出的参数 + * @param string $msg 如果回调处理失败,可以将错误信息输出到该方法 + * @return true回调出来完成不需要继续回调,false回调处理未完成需要继续回调 + */ + public function NotifyProcess($data, &$msg) + { + //TODO 用户基础该类之后需要重写该方法,成功的时候返回true,失败返回false + return true; + } + + /** + * + * notify回调方法,该方法中需要赋值需要输出的参数,不可重写 + * @param array $data + * @return true回调出来完成不需要继续回调,false回调处理未完成需要继续回调 + */ + final public function NotifyCallBack($data) + { + $msg = "OK"; + + $aaa=json_encode($data); + + file_put_contents('./weixinQuery.txt','NotifyCallBack:data---:'.$aaa,FILE_APPEND); + + $result = $this->NotifyProcess($data, $msg); + + if($result == true){ + $this->SetReturn_code("SUCCESS"); + $this->SetReturn_msg("OK"); + } else { + $this->SetReturn_code("FAIL"); + $this->SetReturn_msg($msg); + } + return $result; + } + + /** + * + * 回复通知 + * @param bool $needSign 是否需要签名输出 + */ + final private function ReplyNotify($needSign = true) + { + //如果需要签名 + if($needSign == true && + $this->GetReturn_code($return_code) == "SUCCESS") + { + $this->SetSign(); + } + WxpayApi::replyNotify($this->ToXml()); + } +} \ No newline at end of file